Files
AnyPS5/core/libs/tests/GuestShmOpen.cpp
T
Muhammad Wasif Ali WasifandClaude Opus 5.5 ddb774ad26 feat(libkernel): map regular files with mmap on Windows
A shared mapping is a view of a file section created on the descriptor's
handle, read-write when the descriptor is, otherwise read-only. A private
mapping is a copy of the file read at mmap, as POSIX allows. Anonymous
shm_open objects and JIT shared memory descriptors are temporary files on
Windows, so mmap now maps them too (it failed with EOPNOTSUPP before).
mprotect of a mapped view above the view's protection fails with EACCES
before any bookkeeping changes. The cases Windows cannot represent are
listed in the libkernel debt.

Dusklight (PPSA99305) uses SQLite in WAL mode, which maps its -shm file.
On Windows main f0634088b every start logged "Failed to start
transaction: disk I/O error" for its Dawn and pipeline caches (3 of 3
runs); with this change the caches initialise and their WAL files fill
(4 of 4 runs). The title reaches its menu either way.

A shared mapping may end inside the 16 KiB page that holds the end of
the file, as POSIX mmap allows: the view ends with the file's last 4 KiB
host page, which Windows zero-fills past the end, the rest of the guest
page is private zero pages, and the file never grows. A mapping that
reaches a whole page past the end still fails with EOPNOTSUPP.

PS5LM Plus (PPSA99581) loads its model with llama.cpp, which maps the
whole GGUF file shared and read-only. With a 491,400,032-byte model,
Windows main 5de7a46e0 and this change without the last page logged
"llama_model_load: error loading model: mmap failed: Operation not
supported" (3 of 3 runs); now the model loads and the completion of
"The capital of France is" starts with " Paris" (3 of 3 runs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 05:02:25 +05:00

74 lines
3.4 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
extern "C" {
int APS5_VABI shm_open_nid_postfix(const char*, int, int);
int APS5_VABI ftruncate_nid_postfix(int, std::int64_t);
std::int64_t APS5_VABI pread_nid_postfix(int, void*, std::size_t, std::int64_t);
std::int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int, const void*, std::size_t, std::int64_t);
int APS5_VABI close_nid_postfix(int);
void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept;
int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept;
int* APS5_VABI __error_nid_postfix();
}
static void Require(bool value) { if (!value) std::abort(); }
static const char* const anonymous = reinterpret_cast<const char*>(1);
static void RequireInvalid(const char* path, int flags) {
*__error_nid_postfix() = 0;
Require(shm_open_nid_postfix(path, flags, 0600) == -1);
Require(*__error_nid_postfix() == 22);
}
int main() {
*__error_nid_postfix() = 13;
const int first = shm_open_nid_postfix(anonymous, 0x100202, 0600);
const int second = shm_open_nid_postfix(anonymous, 0x2, 0);
const int third = shm_open_nid_postfix(anonymous, 0x100e02, 0400);
Require(first >= 0 && second >= 0 && third >= 0);
Require(first != second && second != third && first != third);
Require(*__error_nid_postfix() == 13);
Require(ftruncate_nid_postfix(first, 0x4000) == 0);
const char text[] = "anonymous";
Require(pwrite_nid_disambig1_nid_postfix(first, text, sizeof(text), 0x1000) == static_cast<std::int64_t>(sizeof(text)));
char back[sizeof(text)] = {};
Require(pread_nid_postfix(first, back, sizeof(back), 0x1000) == static_cast<std::int64_t>(sizeof(back)));
Require(std::memcmp(back, text, sizeof(text)) == 0);
char last = 1;
Require(pread_nid_postfix(first, &last, 1, 0x3fff) == 1 && last == 0);
Require(pread_nid_postfix(first, &last, 1, 0x4000) == 0);
Require(pread_nid_postfix(second, back, sizeof(back), 0) == 0);
auto* view = static_cast<volatile char*>(mmap_nid_postfix(nullptr, 0x4000, 3, 0x1, first, 0));
Require(view != reinterpret_cast<void*>(static_cast<std::uintptr_t>(-1)));
Require(view[0x1000] == 'a' && view[0x3fff] == 0);
view[0x2000] = 'm';
Require(pread_nid_postfix(first, &last, 1, 0x2000) == 1 && last == 'm');
Require(munmap_nid_postfix(const_cast<char*>(view), 0x4000) == 0);
#ifndef _WIN32
Require(pwrite_nid_disambig1_nid_postfix(third, text, sizeof(text), 0) == 0);
#endif
Require(ftruncate_nid_postfix(third, 0x4000) == 0);
Require(pwrite_nid_disambig1_nid_postfix(third, text, sizeof(text), 0) == static_cast<std::int64_t>(sizeof(text)));
Require(close_nid_postfix(first) == 0 && close_nid_postfix(second) == 0 && close_nid_postfix(third) == 0);
RequireInvalid(anonymous, 0x0);
RequireInvalid(anonymous, 0x200);
RequireInvalid(anonymous, 0x1);
RequireInvalid(anonymous, 0x3);
RequireInvalid(anonymous, 0x2 | 0x8);
RequireInvalid(anonymous, 0x2 | 0x4);
RequireInvalid("/anyps5-shm", 0x1);
RequireInvalid("/anyps5-shm", 0x202 | 0x8);
#ifdef _WIN32
bool threw = false;
try {
shm_open_nid_postfix("/anyps5-shm", 0x202, 0600);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
#else
RequireInvalid("", 0x202);
#endif
}