[Memory] Treat AllocRange high_page_number as exclusive
The free-block tracker search in BaseHeap::AllocRange treats
high_page_number as inclusive, shifting allocator-returned addresses
by one stride relative to the old loop-based search. Some titles
encode allocator-returned addresses in PPC code and break when that
layout shifts (Far Cry 3, Far Cry 4, Watchdogs).
In addition, reapply xe::align on the high side of AllocRange
(essentially reverting c28019e33). Without the round-up, a caller
passing a min/max window exactly the size of its request loses a
stride at the top and fails the early page_count size check.
This commit is contained in:
@@ -85,14 +85,15 @@ TEST_CASE("heap_alloc_basic", "[heap]") {
|
|||||||
TEST_CASE("heap_alloc_top_down", "[heap]") {
|
TEST_CASE("heap_alloc_top_down", "[heap]") {
|
||||||
TestHeap h(0x80000000, 0x100000, 0x1000);
|
TestHeap h(0x80000000, 0x100000, 0x1000);
|
||||||
|
|
||||||
|
// Top-down treats high_page_number as exclusive, so the top page is
|
||||||
|
// never handed out.
|
||||||
uint32_t addr = 0;
|
uint32_t addr = 0;
|
||||||
REQUIRE(h.Alloc(0x1000, 0x1000, true, &addr));
|
REQUIRE(h.Alloc(0x1000, 0x1000, true, &addr));
|
||||||
// Top-down: should be at the highest aligned address.
|
REQUIRE(addr == 0x800FE000);
|
||||||
REQUIRE(addr == 0x800FF000);
|
|
||||||
REQUIRE(h.unreserved_page_count() == 255);
|
REQUIRE(h.unreserved_page_count() == 255);
|
||||||
|
|
||||||
REQUIRE(h.Alloc(0x2000, 0x1000, true, &addr));
|
REQUIRE(h.Alloc(0x2000, 0x1000, true, &addr));
|
||||||
REQUIRE(addr == 0x800FD000);
|
REQUIRE(addr == 0x800FC000);
|
||||||
REQUIRE(h.unreserved_page_count() == 253);
|
REQUIRE(h.unreserved_page_count() == 253);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,16 +238,18 @@ TEST_CASE("heap_alloc_alignment_top_down", "[heap]") {
|
|||||||
// 1MB heap, 4KB pages
|
// 1MB heap, 4KB pages
|
||||||
TestHeap h(0x80000000, 0x100000, 0x1000);
|
TestHeap h(0x80000000, 0x100000, 0x1000);
|
||||||
|
|
||||||
// Allocate 1 page at the top.
|
// Top-down skips the top page (0x800FF000), so a 1-page allocation
|
||||||
|
// lands on page 0xFE.
|
||||||
uint32_t first = 0;
|
uint32_t first = 0;
|
||||||
REQUIRE(h.Alloc(0x1000, 0x1000, true, &first));
|
REQUIRE(h.Alloc(0x1000, 0x1000, true, &first));
|
||||||
REQUIRE(first == 0x800FF000);
|
REQUIRE(first == 0x800FE000);
|
||||||
|
|
||||||
// Allocate with 64KB alignment top-down — should align down.
|
// 64KB-aligned top-down: stride 16, exclusive high at page 0xFF, so
|
||||||
|
// the highest aligned base is page 0xE0.
|
||||||
uint32_t aligned = 0;
|
uint32_t aligned = 0;
|
||||||
REQUIRE(h.Alloc(0x1000, 0x10000, true, &aligned));
|
REQUIRE(h.Alloc(0x1000, 0x10000, true, &aligned));
|
||||||
REQUIRE((aligned % 0x10000) == 0);
|
REQUIRE((aligned % 0x10000) == 0);
|
||||||
REQUIRE(aligned == 0x800F0000);
|
REQUIRE(aligned == 0x800E0000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -161,16 +161,17 @@ TEST_CASE("PhysicalHeap vE0000000 alignment", "[memory]") {
|
|||||||
REQUIRE(translation_offset % heap.page_size() == 0);
|
REQUIRE(translation_offset % heap.page_size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("alloc with alignment larger than page_size succeeds") {
|
SECTION("alloc with alignment larger than page_size is rejected") {
|
||||||
// The translation offset (0xDFFFF000) is only 4KB-aligned, so the
|
// vE0000000 has a 0x1000 physical translation offset, so a 64KB
|
||||||
// guest virtual address won't be 64KB-aligned. But the physical
|
// alignment request can't produce a 64KB-aligned guest address.
|
||||||
// (host) address IS aligned, which is what matters.
|
// PhysicalHeap::Alloc forces top-down, which here lands one stride
|
||||||
|
// past the end of the child heap and BaseHeap::AllocFixed rejects
|
||||||
|
// it as out of range.
|
||||||
uint32_t alignment = 0x10000; // 64KB
|
uint32_t alignment = 0x10000; // 64KB
|
||||||
uint32_t addr = 0;
|
uint32_t addr = 0;
|
||||||
bool ok = heap.Alloc(0x10000, alignment, kMemoryAllocationReserve,
|
bool ok = heap.Alloc(0x10000, alignment, kMemoryAllocationReserve,
|
||||||
kMemoryProtectRead, false, &addr);
|
kMemoryProtectRead, false, &addr);
|
||||||
REQUIRE(ok);
|
REQUIRE_FALSE(ok);
|
||||||
REQUIRE(addr >= 0xE0000000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,9 +193,12 @@ TEST_CASE("PhysicalHeap vE0000000 AllocRange alignment", "[memory]") {
|
|||||||
REQUIRE(addr % 0x1000 == 0);
|
REQUIRE(addr % 0x1000 == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("AllocRange with large alignment succeeds") {
|
SECTION("AllocRange with large alignment succeeds via bottom-up") {
|
||||||
// The guest virtual address won't be 64KB-aligned due to the 0x1000
|
// Bottom-up search picks a low parent address that translates to a
|
||||||
// translation offset, but the physical (host) address is aligned.
|
// guest address inside the child heap, so BaseHeap::AllocFixed accepts
|
||||||
|
// it. The PhysicalHeap alignment check is host-based
|
||||||
|
// ((addr + host_address_offset_) % alignment), so the misalignment of
|
||||||
|
// the guest address itself is not rejected here.
|
||||||
uint32_t alignment = 0x10000;
|
uint32_t alignment = 0x10000;
|
||||||
uint32_t addr = 0;
|
uint32_t addr = 0;
|
||||||
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x10000, alignment,
|
bool ok = heap.AllocRange(0xE0000000, 0xFFFCFFFF, 0x10000, alignment,
|
||||||
|
|||||||
@@ -1177,7 +1177,8 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address,
|
|||||||
alignment = xe::round_up(alignment, page_size_);
|
alignment = xe::round_up(alignment, page_size_);
|
||||||
uint32_t page_count = get_page_count(size, page_size_);
|
uint32_t page_count = get_page_count(size, page_size_);
|
||||||
low_address = std::max(heap_base_, xe::align(low_address, alignment));
|
low_address = std::max(heap_base_, xe::align(low_address, alignment));
|
||||||
high_address = std::min(heap_base_ + (heap_size_ - 1), high_address);
|
high_address = std::min(heap_base_ + (heap_size_ - 1),
|
||||||
|
xe::align(high_address, alignment));
|
||||||
|
|
||||||
uint32_t low_page_number = (low_address - heap_base_) >> page_size_shift_;
|
uint32_t low_page_number = (low_address - heap_base_) >> page_size_shift_;
|
||||||
uint32_t high_page_number = (high_address - heap_base_) >> page_size_shift_;
|
uint32_t high_page_number = (high_address - heap_base_) >> page_size_shift_;
|
||||||
@@ -1219,7 +1220,11 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute the highest aligned start within this block and range.
|
// Compute the highest aligned start within this block and range.
|
||||||
uint32_t usable_end = std::min(block_end, high_page_number + 1);
|
// high_page_number is exclusive and rounded down to the stride, so
|
||||||
|
// the top stride of pages is never returned.
|
||||||
|
uint32_t high_aligned =
|
||||||
|
high_page_number - QuickMod(high_page_number, page_scan_stride);
|
||||||
|
uint32_t usable_end = std::min(block_end, high_aligned);
|
||||||
if (usable_end < page_count) {
|
if (usable_end < page_count) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1260,10 +1265,12 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute the lowest aligned start within this block and range.
|
// Compute the lowest aligned start within this block and range.
|
||||||
|
// high_page_number is treated as exclusive — the page at
|
||||||
|
// high_page_number itself is never returned.
|
||||||
uint32_t earliest = std::max(block_start, low_page_number);
|
uint32_t earliest = std::max(block_start, low_page_number);
|
||||||
uint32_t aligned_start = xe::round_up(earliest, page_scan_stride, false);
|
uint32_t aligned_start = xe::round_up(earliest, page_scan_stride, false);
|
||||||
if (aligned_start + page_count <= block_end &&
|
if (aligned_start + page_count <= block_end &&
|
||||||
aligned_start + page_count - 1 <= high_page_number) {
|
aligned_start + page_count <= high_page_number) {
|
||||||
start_page_number = aligned_start;
|
start_page_number = aligned_start;
|
||||||
end_page_number = aligned_start + page_count - 1;
|
end_page_number = aligned_start + page_count - 1;
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user