re: the leaking release is confirmed in Xenia's source, not inferred

The previous write-up argued the leak from adjacency -- 22 failed releases next
to the failed allocation.  memory.cc closes the loop outright:

  * the "parent free N/M pages" in the error is
    parent_heap_->unreserved_page_count() (memory.cc:1807);
  * unreserved_page_count_ is incremented in exactly ONE place, memory.cc:1445,
    inside BaseHeap::Release's page-table loop;
  * the failing path returns at memory.cc:1399, at the top of that same
    function, before the loop -- page table untouched, no free block inserted;
  * and PhysicalHeap::Release delegates to parent_heap_->Release, so the release
    that fails and the allocation that later comes up short are the same heap.

So every "address is not a region start" returns zero pages to the counter the
allocator consults, and those pages stay reserved for the life of the process.
That is control flow, not correlation.

Magnitude is still open and I am not claiming it: 512 - 113 = 399 MB missing
against only 22-23 failed releases would need ~18 MB average each, which is
implausible as the whole story.  Leaked releases are a contributor, maybe not
the dominant one.  An allocation ledger -- log every MmAllocatePhysicalMemoryEx
and MmFreePhysicalMemory with sizes and balance them -- would settle it, and is
a better use of a run than reproducing the freeze again.

Also recorded so nobody hunts for it: there is NO cvar for guest memory size.
memory.cc has only protect_zero / protect_on_release / scribble_heap and the
MMIO ones, and xboxkrnl_memory.cc says "We don't support separate devkit
memory, so just ignore this flag".  512 MB is hardcoded to the retail console,
so the freeze cannot be dodged by giving the emulator more -- a fix has to be
the release path itself.
This commit is contained in:
Sylpheed RE agent
2026-08-26 11:32:34 +00:00
parent 1ce7d7c54c
commit 419d45eae5

View File

@@ -122,6 +122,77 @@ So `parent heap` is **the emulated console's physical memory**, not anything of
the host's. The game runs a real 512 MB console down to 113 MB free and then asks
for 128 MB. Nothing about the container is involved.
## ✅ The mechanism, confirmed in Xenia's source — the failed release leaks, provably
The adjacency argument below is no longer the evidence. `src/xenia/memory.cc`
closes the loop by itself.
**1. The number in the error is `unreserved_page_count_`.** The message is
emitted at `memory.cc:1807`:
```cpp
XELOGE("PhysicalHeap::Alloc unable to alloc physical memory in parent heap "
"(requested {} bytes, parent free {}/{} pages)",
size, parent_heap_->unreserved_page_count(),
parent_heap_->total_page_count());
```
So the `28969/131072` in the log *is* that counter, on the **parent** heap.
**2. That counter is incremented in exactly one place**`memory.cc:1445`,
inside `BaseHeap::Release`'s page-table loop:
```cpp
for (uint32_t page_number = base_page_number; page_number <= end_page_number;
++page_number) {
auto& page_entry = page_table_[page_number];
page_entry.qword = 0;
unreserved_page_count_++; // <- the only increment
}
InsertFreeBlock(base_page_number, base_page_entry.region_page_count);
```
**3. The failing path returns before reaching it**`memory.cc:1399`, the very
top of the same function:
```cpp
if (base_page_entry.base_address != base_page_number) {
XELOGE("BaseHeap::Release failed because address is not a region start");
return false; // <- page table untouched, no free block
}
```
**4. And it is the same heap.** `PhysicalHeap::Release` delegates to
`parent_heap_->Release(...)`, so the release that fails and the allocation that
later comes up short are against the same 512 MB parent heap.
Therefore every `address is not a region start` is a release that returns
**zero** pages to the counter the allocator later consults. The pages stay
reserved for the life of the process. This is not an inference from adjacency —
it is the control flow.
## 🟡 What is still not measured: the magnitude
Mechanism confirmed, quantity not. 512 113 = **399 MB** is unaccounted for, and
there are only **2223** failed releases per run, which would need an implausible
~18 MB average each to explain the whole deficit alone. So leaked releases are
**a** contributor and possibly not the dominant one; the game may simply hold a
lot of live memory at stage load.
The experiment that would settle it is an allocation ledger — log every
`MmAllocatePhysicalMemoryEx` / `MmFreePhysicalMemory` with its size and balance
the books — rather than more freeze reproductions.
## 🔴 There is no memory-size knob to work around it
Worth stating so nobody looks: Xenia has **no cvar for guest memory size**. The
only memory cvars in `memory.cc` are `protect_zero`, `protect_on_release`,
`scribble_heap` and the MMIO ones, and
`xboxkrnl_memory.cc` says outright *"We don't support separate devkit memory, so
just ignore this flag"* when a game asks for the devkit's extra RAM. The 512 MB
is hardcoded to the retail console. So the freeze cannot be dodged by giving the
emulator more — the fix has to be the release path, in a canary build.
## 🟡 The likely mechanism: a release path that leaks
Immediately before the failed allocation, in **both** runs: