342 lines
13 KiB
C++
342 lines
13 KiB
C++
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
|
|
#include "prx/libkernel/DirectMemory/MemoryPool.hpp"
|
|
#include "prx/libkernel/KernelErrors.hpp"
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <new>
|
|
#include <stdexcept>
|
|
|
|
bool DirectMemoryCheckedFree(int64_t start, size_t len);
|
|
void DirectMemoryRetype(int64_t start, size_t len, int memoryType);
|
|
size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit);
|
|
|
|
namespace {
|
|
|
|
constexpr uint64_t kPage = static_cast<uint64_t>(PS5_PAGE_SIZE);
|
|
constexpr uint64_t kPool = static_cast<uint64_t>(DIRECT_MEMORY_SIZE);
|
|
|
|
enum class Fault { None, BackingBadAlloc, BackingRuntimeError, ForgetRuntimeError };
|
|
Fault g_fault = Fault::None;
|
|
long g_newBudget = -1;
|
|
|
|
int g_failures = 0;
|
|
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
std::printf(" FAILED line %d: %s\n", __LINE__, #cond); \
|
|
++g_failures; \
|
|
} \
|
|
} while (0)
|
|
|
|
struct NewBudget {
|
|
explicit NewBudget(long n) { g_newBudget = n; }
|
|
~NewBudget() { g_newBudget = -1; }
|
|
};
|
|
|
|
bool BlockAt(uint64_t offset, int64_t& s, int64_t& e, int& t) {
|
|
return DirectMemoryFind(static_cast<int64_t>(offset), false, &s, &e, &t);
|
|
}
|
|
|
|
bool BlockIs(uint64_t offset, uint64_t start, uint64_t end, int type) {
|
|
int64_t s = 0, e = 0;
|
|
int t = 0;
|
|
return BlockAt(offset, s, e, t) && s == static_cast<int64_t>(start) && e == static_cast<int64_t>(end) && t == type;
|
|
}
|
|
|
|
bool PoolEmpty() {
|
|
int64_t s, e;
|
|
int t;
|
|
return !DirectMemoryFind(0, true, &s, &e, &t) && DirectMemoryFreeRun(0, kPool) == kPool;
|
|
}
|
|
|
|
void alloc_unplaceable_requests() {
|
|
int64_t out = -1;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 0, 0, 1, &out) == SCE_KERNEL_ERROR_EINVAL);
|
|
CHECK(DirectMemoryAlloc(-1, kPool, kPage, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(DirectMemoryAlloc(0, kPool, SIZE_MAX, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPool + kPage, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(DirectMemoryAlloc(kPage, kPage, kPage, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(DirectMemoryAlloc(2 * kPage, kPage, kPage, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(DirectMemoryAlloc(kPool, kPool + kPage, kPage, 0, 1, &out) == SCE_KERNEL_ERROR_EAGAIN);
|
|
CHECK(out == -1);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void alloc_null_out() {
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPage, 0, 1, nullptr) == SCE_KERNEL_ERROR_EFAULT);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void alloc_rounding_alignment_and_clamping() {
|
|
int64_t a = -1, b = -1;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 1, 1, 1, &a) == 0 && a == 0);
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPage, 0, 1, &b) == 0 && b == static_cast<int64_t>(kPage));
|
|
DirectMemoryFree(0, kPool);
|
|
|
|
CHECK(DirectMemoryAlloc(0, -1, kPage, 0, 1, &a) == 0 && a == 0);
|
|
DirectMemoryFree(0, kPage);
|
|
CHECK(DirectMemoryAlloc(0, INT64_MAX, kPage, 0, 1, &a) == 0 && a == 0);
|
|
DirectMemoryFree(0, kPage);
|
|
|
|
CHECK(DirectMemoryAlloc(1, kPool, kPage, 0, 1, &a) == 0 && a == static_cast<int64_t>(kPage));
|
|
DirectMemoryFree(0, kPool);
|
|
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPage, static_cast<size_t>(1) << 62, 1, &a) == 0 && a == 0);
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPage, static_cast<size_t>(1) << 62, 1, &b) == SCE_KERNEL_ERROR_EAGAIN);
|
|
DirectMemoryFree(0, kPool);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void alloc_non_power_of_two_alignment() {
|
|
int64_t a = -1, b = -1, c = -1;
|
|
CHECK(DirectMemoryAlloc(kPage, kPool, kPage, 3 * kPage, 1, &a) == 0 && a == static_cast<int64_t>(3 * kPage));
|
|
CHECK(DirectMemoryAlloc(kPage, kPool, kPage, 3 * kPage, 1, &b) == 0 && b == static_cast<int64_t>(6 * kPage));
|
|
CHECK(DirectMemoryAlloc(0, kPool, kPage, 3 * kPage, 1, &c) == 0 && c == 0);
|
|
CHECK(BlockIs(3 * kPage, 3 * kPage, 4 * kPage, 1));
|
|
DirectMemoryFree(0, kPool);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void alloc_exhaustion_and_gap_reuse() {
|
|
int64_t a, b, c;
|
|
CHECK(DirectMemoryAlloc(0, 3 * kPage, kPage, 0, 1, &a) == 0 && a == 0);
|
|
CHECK(DirectMemoryAlloc(0, 3 * kPage, kPage, 0, 1, &b) == 0 && b == static_cast<int64_t>(kPage));
|
|
CHECK(DirectMemoryAlloc(0, 3 * kPage, kPage, 0, 1, &c) == 0 && c == static_cast<int64_t>(2 * kPage));
|
|
CHECK(DirectMemoryAlloc(0, 3 * kPage, kPage, 0, 1, &c) == SCE_KERNEL_ERROR_EAGAIN);
|
|
DirectMemoryFree(static_cast<int64_t>(kPage), kPage);
|
|
CHECK(DirectMemoryAlloc(0, 3 * kPage, kPage, 0, 1, &c) == 0 && c == static_cast<int64_t>(kPage));
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void free_out_of_range_is_ignored() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 7, &a) == 0 && a == 0);
|
|
DirectMemoryFree(0, SIZE_MAX);
|
|
DirectMemoryFree(static_cast<int64_t>(kPool), kPage);
|
|
DirectMemoryFree(static_cast<int64_t>(kPool - kPage), 2 * kPage);
|
|
DirectMemoryFree(INT64_MAX, kPage);
|
|
CHECK(!DirectMemoryCheckedFree(0, SIZE_MAX));
|
|
CHECK(!DirectMemoryCheckedFree(static_cast<int64_t>(kPool), kPage));
|
|
CHECK(BlockIs(0, 0, 4 * kPage, 7));
|
|
DirectMemoryFree(0, kPool);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void retype_out_of_range_is_ignored() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 7, &a) == 0 && a == 0);
|
|
DirectMemoryRetype(0, SIZE_MAX, 9);
|
|
DirectMemoryRetype(static_cast<int64_t>(kPool), kPage, 9);
|
|
DirectMemoryRetype(static_cast<int64_t>(kPool - kPage), 2 * kPage, 9);
|
|
CHECK(BlockIs(0, 0, 4 * kPage, 7));
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void free_partial_and_spanning() {
|
|
int64_t a, b;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &a) == 0 && a == 0);
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 2, &b) == 0 && b == static_cast<int64_t>(4 * kPage));
|
|
DirectMemoryFree(static_cast<int64_t>(2 * kPage), 4 * kPage);
|
|
CHECK(BlockIs(0, 0, 2 * kPage, 1));
|
|
CHECK(BlockIs(6 * kPage, 6 * kPage, 8 * kPage, 2));
|
|
CHECK(DirectMemoryFreeRun(2 * kPage, kPool) == 4 * kPage);
|
|
CHECK(!DirectMemoryCheckedFree(0, 4 * kPage));
|
|
CHECK(DirectMemoryCheckedFree(0, 2 * kPage));
|
|
DirectMemoryFree(0, kPool);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void retype_splits_and_skips_gaps() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(2 * kPage, kPool, 4 * kPage, 0, 1, &a) == 0 && a == static_cast<int64_t>(2 * kPage));
|
|
DirectMemoryRetype(static_cast<int64_t>(3 * kPage), kPage, 9);
|
|
CHECK(BlockIs(2 * kPage, 2 * kPage, 3 * kPage, 1));
|
|
CHECK(BlockIs(3 * kPage, 3 * kPage, 4 * kPage, 9));
|
|
CHECK(BlockIs(4 * kPage, 4 * kPage, 6 * kPage, 1));
|
|
DirectMemoryRetype(0, 8 * kPage, 5);
|
|
CHECK(BlockIs(2 * kPage, 2 * kPage, 3 * kPage, 5));
|
|
CHECK(BlockIs(5 * kPage, 4 * kPage, 6 * kPage, 5));
|
|
int64_t s, e;
|
|
int t;
|
|
CHECK(!BlockAt(0, s, e, t) && !BlockAt(6 * kPage, s, e, t));
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void alloc_backing_bad_alloc_rolls_back() {
|
|
int64_t out = -1;
|
|
g_fault = Fault::BackingBadAlloc;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &out) == SCE_KERNEL_ERROR_ENOMEM);
|
|
g_fault = Fault::None;
|
|
CHECK(out == -1);
|
|
CHECK(PoolEmpty());
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &out) == 0 && out == 0);
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void alloc_node_bad_alloc_rolls_back() {
|
|
int64_t out = -1;
|
|
{
|
|
NewBudget budget(0);
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &out) == SCE_KERNEL_ERROR_ENOMEM);
|
|
}
|
|
CHECK(out == -1);
|
|
CHECK(PoolEmpty());
|
|
CHECK(DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &out) == 0 && out == 0);
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void alloc_unexpected_exception_propagates() {
|
|
int64_t out = -1;
|
|
g_fault = Fault::BackingRuntimeError;
|
|
bool threw = false;
|
|
try {
|
|
(void)DirectMemoryAlloc(0, kPool, 4 * kPage, 0, 1, &out);
|
|
} catch (const std::runtime_error&) {
|
|
threw = true;
|
|
}
|
|
g_fault = Fault::None;
|
|
CHECK(threw);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
void free_node_bad_alloc_rolls_back() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 8 * kPage, 0, 3, &a) == 0 && a == 0);
|
|
for (long budget = 0; budget < 2; ++budget) {
|
|
bool threw = false;
|
|
{
|
|
NewBudget guard(budget);
|
|
try {
|
|
DirectMemoryFree(static_cast<int64_t>(2 * kPage), 2 * kPage);
|
|
} catch (const std::bad_alloc&) {
|
|
threw = true;
|
|
}
|
|
}
|
|
CHECK(threw);
|
|
CHECK(BlockIs(0, 0, 8 * kPage, 3));
|
|
CHECK(DirectMemoryFreeRun(2 * kPage, 4 * kPage) == 0);
|
|
}
|
|
DirectMemoryFree(static_cast<int64_t>(2 * kPage), 2 * kPage);
|
|
CHECK(BlockIs(0, 0, 2 * kPage, 3));
|
|
CHECK(BlockIs(4 * kPage, 4 * kPage, 8 * kPage, 3));
|
|
CHECK(DirectMemoryFreeRun(2 * kPage, 4 * kPage) == 2 * kPage);
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void retype_node_bad_alloc_rolls_back() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 8 * kPage, 0, 3, &a) == 0 && a == 0);
|
|
for (long budget = 0; budget < 2; ++budget) {
|
|
bool threw = false;
|
|
{
|
|
NewBudget guard(budget);
|
|
try {
|
|
DirectMemoryRetype(static_cast<int64_t>(2 * kPage), 2 * kPage, 9);
|
|
} catch (const std::bad_alloc&) {
|
|
threw = true;
|
|
}
|
|
}
|
|
CHECK(threw);
|
|
CHECK(BlockIs(0, 0, 8 * kPage, 3));
|
|
}
|
|
DirectMemoryRetype(static_cast<int64_t>(2 * kPage), 2 * kPage, 9);
|
|
CHECK(BlockIs(2 * kPage, 2 * kPage, 4 * kPage, 9));
|
|
DirectMemoryFree(0, kPool);
|
|
}
|
|
|
|
void forget_exception_propagates_and_rolls_back() {
|
|
int64_t a;
|
|
CHECK(DirectMemoryAlloc(0, kPool, 8 * kPage, 0, 3, &a) == 0 && a == 0);
|
|
g_fault = Fault::ForgetRuntimeError;
|
|
bool threwFree = false, threwChecked = false;
|
|
try {
|
|
DirectMemoryFree(static_cast<int64_t>(2 * kPage), 2 * kPage);
|
|
} catch (const std::runtime_error&) {
|
|
threwFree = true;
|
|
}
|
|
try {
|
|
(void)DirectMemoryCheckedFree(static_cast<int64_t>(2 * kPage), 2 * kPage);
|
|
} catch (const std::runtime_error&) {
|
|
threwChecked = true;
|
|
}
|
|
g_fault = Fault::None;
|
|
CHECK(threwFree);
|
|
CHECK(threwChecked);
|
|
CHECK(BlockIs(0, 0, 8 * kPage, 3));
|
|
DirectMemoryFree(0, kPool);
|
|
CHECK(PoolEmpty());
|
|
}
|
|
|
|
struct Case {
|
|
const char* name;
|
|
void (*fn)();
|
|
};
|
|
|
|
const Case kCases[] = {
|
|
{"alloc_unplaceable_requests", alloc_unplaceable_requests},
|
|
{"alloc_null_out", alloc_null_out},
|
|
{"alloc_rounding_alignment_and_clamping", alloc_rounding_alignment_and_clamping},
|
|
{"alloc_non_power_of_two_alignment", alloc_non_power_of_two_alignment},
|
|
{"alloc_exhaustion_and_gap_reuse", alloc_exhaustion_and_gap_reuse},
|
|
{"free_out_of_range_is_ignored", free_out_of_range_is_ignored},
|
|
{"retype_out_of_range_is_ignored", retype_out_of_range_is_ignored},
|
|
{"free_partial_and_spanning", free_partial_and_spanning},
|
|
{"retype_splits_and_skips_gaps", retype_splits_and_skips_gaps},
|
|
{"alloc_backing_bad_alloc_rolls_back", alloc_backing_bad_alloc_rolls_back},
|
|
{"alloc_node_bad_alloc_rolls_back", alloc_node_bad_alloc_rolls_back},
|
|
{"alloc_unexpected_exception_propagates", alloc_unexpected_exception_propagates},
|
|
{"free_node_bad_alloc_rolls_back", free_node_bad_alloc_rolls_back},
|
|
{"retype_node_bad_alloc_rolls_back", retype_node_bad_alloc_rolls_back},
|
|
{"forget_exception_propagates_and_rolls_back", forget_exception_propagates_and_rolls_back},
|
|
};
|
|
|
|
}
|
|
|
|
void CreateDirectMemoryBacking(int64_t, size_t, int) {
|
|
if (g_fault == Fault::BackingBadAlloc) throw std::bad_alloc();
|
|
if (g_fault == Fault::BackingRuntimeError) throw std::runtime_error("backing");
|
|
}
|
|
|
|
void ForgetDirectMemory(int64_t, size_t) {
|
|
if (g_fault == Fault::ForgetRuntimeError) throw std::runtime_error("forget");
|
|
}
|
|
|
|
void* operator new(size_t n) {
|
|
if (g_newBudget == 0) throw std::bad_alloc();
|
|
if (g_newBudget > 0) --g_newBudget;
|
|
void* p = std::malloc(n ? n : 1);
|
|
if (!p) throw std::bad_alloc();
|
|
return p;
|
|
}
|
|
void operator delete(void* p) noexcept { std::free(p); }
|
|
void operator delete(void* p, size_t) noexcept { std::free(p); }
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc == 2 && std::strcmp(argv[1], "--list") == 0) {
|
|
for (const Case& c : kCases) std::printf("%s\n", c.name);
|
|
return 0;
|
|
}
|
|
int ran = 0;
|
|
for (const Case& c : kCases) {
|
|
if (argc == 2 && std::strcmp(argv[1], c.name) != 0) continue;
|
|
std::printf("[ RUN ] %s\n", c.name);
|
|
const int before = g_failures;
|
|
try {
|
|
c.fn();
|
|
} catch (...) {
|
|
std::printf(" FAILED: unexpected exception escaped\n");
|
|
++g_failures;
|
|
}
|
|
std::printf("[ %s ] %s\n", g_failures == before ? " OK " : "FAIL", c.name);
|
|
++ran;
|
|
}
|
|
if (ran == 0) {
|
|
std::printf("no such case\n");
|
|
return 2;
|
|
}
|
|
return g_failures == 0 ? 0 : 1;
|
|
}
|