Files
Xenia-Canary/src/xenia/base/testing/physical_heap_test.cc
Herman S. c28019e333 [Memory] Fix AllocRange upper bound and update PhysicalHeap tests
BaseHeap::AllocRange was rounding the inclusive high_address UP via
xe::align(), which could push the search range one alignment stride
past the requested end. The new free-block-tracker top-down search
treats high_page_number as an inclusive max (usable_end =
high_page_number + 1), so the round-up caused it to return a base
page one stride beyond the caller's bound — most visibly when
PhysicalHeap::Alloc passed parent_heap_end = GetPhysicalAddress(...)
just below a page boundary, producing a translated address one page
past the child heap and tripping "passed out of range address range"
in BaseHeap::AllocFixed.

Drop the xe::align on the high side so high_address stays a true
inclusive bound. The low_address round-up is still correct since
allocations must START at or above the aligned low.
2026-04-13 11:38:01 +09:00

210 lines
7.7 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2026 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/memory.h"
#include "third_party/catch/include/catch.hpp"
#include "xenia/base/memory.h"
namespace xe {
namespace test {
// All tests use kMemoryAllocationReserve which only touches the page table,
// not host memory. This lets us pass nullptr for membase and Memory*.
TEST_CASE("PhysicalHeap::GetPhysicalAddress", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
SECTION("heap with no offset returns heap-relative address") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
0x20000000, 64 * 1024, &parent);
REQUIRE(heap.host_address_offset() == 0);
REQUIRE(heap.GetPhysicalAddress(0xA0000000) == 0);
REQUIRE(heap.GetPhysicalAddress(0xA0010000) == 0x10000);
REQUIRE(heap.GetPhysicalAddress(0xA1000000) == 0x1000000);
}
SECTION("0xE0000000 heap always has 0x1000 physical offset") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
0x1FD00000, 4096, &parent);
// The 0x1000 physical offset is baked into the view mapping
// (map_info target_address), not derived from host_address_offset.
REQUIRE(heap.GetPhysicalAddress(0xE0000000) == 0x1000);
REQUIRE(heap.GetPhysicalAddress(0xE0001000) == 0x2000);
REQUIRE(heap.GetPhysicalAddress(0xE0010000) == 0x11000);
}
}
TEST_CASE("PhysicalHeap::Alloc alignment", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
SECTION("returned address is page-aligned") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
0x20000000, 64 * 1024, &parent);
uint32_t addr = 0;
bool ok = heap.Alloc(0x10000, 0x10000, kMemoryAllocationReserve,
kMemoryProtectRead, false, &addr);
REQUIRE(ok);
REQUIRE(addr != 0);
REQUIRE(addr % 0x10000 == 0);
REQUIRE(addr >= 0xA0000000);
REQUIRE(addr < 0xC0000000);
}
SECTION("multiple allocations with different alignments") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
0x20000000, 64 * 1024, &parent);
for (uint32_t alignment : {0x10000u, 0x20000u, 0x40000u, 0x100000u}) {
uint32_t addr = 0;
bool ok = heap.Alloc(alignment, alignment, kMemoryAllocationReserve,
kMemoryProtectRead, false, &addr);
REQUIRE(ok);
REQUIRE(addr % alignment == 0);
}
}
}
TEST_CASE("PhysicalHeap::AllocRange alignment", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
SECTION("returned address respects alignment within range") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
0x20000000, 64 * 1024, &parent);
uint32_t addr = 0;
bool ok = heap.AllocRange(0xA0000000, 0xBFFFFFFF, 0x10000, 0x10000,
kMemoryAllocationReserve, kMemoryProtectRead,
false, &addr);
REQUIRE(ok);
REQUIRE(addr % 0x10000 == 0);
REQUIRE(addr >= 0xA0000000);
REQUIRE(addr <= 0xBFFFFFFF);
}
SECTION("large alignment preserved through translation") {
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xC0000000,
0x20000000, 16 * 1024 * 1024, &parent);
uint32_t addr = 0;
bool ok = heap.AllocRange(0xC0000000, 0xDFFFFFFF, 0x1000000, 0x1000000,
kMemoryAllocationReserve, kMemoryProtectRead,
false, &addr);
REQUIRE(ok);
REQUIRE(addr % 0x1000000 == 0);
}
}
TEST_CASE("PhysicalHeap::AllocFixed alignment", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xA0000000,
0x20000000, 64 * 1024, &parent);
// AllocFixed at a specific aligned address must succeed
bool ok = heap.AllocFixed(0xA0100000, 0x10000, 0x10000,
kMemoryAllocationReserve, kMemoryProtectRead);
REQUIRE(ok);
}
TEST_CASE("PhysicalHeap vE0000000 alignment", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
0x1FD00000, 4096, &parent);
// The 0xE0000000 heap always has a 0x1000 physical offset, so the
// translation offset is 0xE0000000 - 0x1000 = 0xDFFFF000, which is
// 4KB-aligned but not 64KB-aligned. This is true on all platforms.
uint32_t physical_base = heap.GetPhysicalAddress(heap.heap_base());
REQUIRE(physical_base == 0x1000);
SECTION("page-size allocation preserves alignment") {
uint32_t addr = 0;
bool ok = heap.Alloc(0x1000, 0x1000, kMemoryAllocationReserve,
kMemoryProtectRead, false, &addr);
REQUIRE(ok);
REQUIRE(addr % 0x1000 == 0);
REQUIRE(addr >= 0xE0000000);
}
SECTION("translation offset is 4KB-aligned") {
uint32_t translation_offset = heap.heap_base() - physical_base;
REQUIRE(translation_offset % heap.page_size() == 0);
}
SECTION("alloc with alignment larger than page_size succeeds") {
// The translation offset (0xDFFFF000) is only 4KB-aligned, so the
// guest virtual address won't be 64KB-aligned. But the physical
// (host) address IS aligned, which is what matters.
uint32_t alignment = 0x10000; // 64KB
uint32_t addr = 0;
bool ok = heap.Alloc(0x10000, alignment, kMemoryAllocationReserve,
kMemoryProtectRead, false, &addr);
REQUIRE(ok);
REQUIRE(addr >= 0xE0000000);
}
}
TEST_CASE("PhysicalHeap vE0000000 AllocRange alignment", "[memory]") {
VirtualHeap parent;
parent.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0x00000000,
0x20000000, 4096);
PhysicalHeap heap;
heap.Initialize(nullptr, nullptr, HeapType::kGuestPhysical, 0xE0000000,
0x1FD00000, 4096, &parent);
SECTION("page-aligned AllocRange succeeds") {
uint32_t addr = 0;
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x1000, 0x1000,
kMemoryAllocationReserve, kMemoryProtectRead,
false, &addr);
REQUIRE(ok);
REQUIRE(addr % 0x1000 == 0);
}
SECTION("AllocRange with large alignment succeeds") {
// The guest virtual address won't be 64KB-aligned due to the 0x1000
// translation offset, but the physical (host) address is aligned.
uint32_t alignment = 0x10000;
uint32_t addr = 0;
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x10000, alignment,
kMemoryAllocationReserve, kMemoryProtectRead,
false, &addr);
REQUIRE(ok);
REQUIRE(addr >= 0xE0000000);
}
}
} // namespace test
} // namespace xe