Files
AnyPS5/core/libs/prx/libSceAgcDriver/tests/WriteWatchCoverage.cpp
T
Dean Galvin af4e074a02 fix(agc): save pending storage results in every 64 KiB block that leaves the write watch
On Windows the write watch is dropped in whole 64 KiB blocks
(WriteWatchCoverage::Exclude), but the unwatch hook was given the page
range of the import. An image with results pending in the same block as
an imported page, outside that page, lost the watch without its guest
bytes saved, and storing its results threw.

The hook now gets the span of blocks Exclude drops (ExcludedSpan, the same
block math), so every image with results pending in those blocks saves its
bytes first.
2026-10-15 14:01:01 +02:00

70 lines
6.1 KiB
C++

#include "prx/libSceAgcDriver/Execution/include/WriteWatchCoverage.hpp"
#include <iostream>
#include <limits>
#include <stdexcept>
void require(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
int main() {
try {
using AgcDriver::GuestMemory::WriteWatchCoverage;
constexpr std::uint64_t base = 0x200000000;
constexpr std::size_t block = 65536;
WriteWatchCoverage coverage;
require(!coverage.Covers(base, 1), "an uninitialized arena is watched");
coverage.Initialize(base, 4 * block + 1);
require(coverage.Covers(base, 4 * block + 1), "the initialized arena is not watched");
require(!coverage.Covers(base - 1, 1) && !coverage.Covers(base + 4 * block + 1, 1), "outside memory is watched");
require(!coverage.Covers(base, 0) && !coverage.Covers(base, std::numeric_limits<std::size_t>::max()), "an empty or overflowing range is watched");
std::uint64_t spanBegin = 0;
std::uint64_t spanEnd = 0;
require(coverage.ExcludedSpan(base + block + 4096, 4096, &spanBegin, &spanEnd) && spanBegin == base + block && spanEnd == base + 2 * block, "the span of an imported page is not its whole block");
require(coverage.ExcludedSpan(base + 3 * block - 1, 2, &spanBegin, &spanEnd) && spanBegin == base + 2 * block && spanEnd == base + 4 * block, "the span of a boundary import is not both blocks");
require(coverage.ExcludedSpan(base + 4 * block, std::numeric_limits<std::size_t>::max(), &spanBegin, &spanEnd) && spanBegin == base + 4 * block && spanEnd == base + 4 * block + 1, "the span of the last partial block is not clipped to the arena");
require(coverage.ExcludedSpan(base - 1, 2, &spanBegin, &spanEnd) && spanBegin == base && spanEnd == base + block, "the span of an import crossing the arena start is not clipped");
require(!coverage.ExcludedSpan(base - block, block, &spanBegin, &spanEnd) && !coverage.ExcludedSpan(base + 5 * block, block, &spanBegin, &spanEnd) && !coverage.ExcludedSpan(base, 0, &spanBegin, &spanEnd), "an outside or empty import has a span");
require(coverage.Exclude(base + block + 4096, 1), "an imported page was not excluded");
require(!coverage.Covers(base + block, block), "the imported block is still watched");
require(!coverage.Covers(base + block - 1, 2), "a range crossing into the import is watched");
require(coverage.Covers(base, block) && coverage.Covers(base + 2 * block, 2 * block + 1), "unrelated blocks lost write watching");
require(!coverage.Exclude(base + block, block), "excluding an imported block again reports a change");
require(coverage.Exclude(base + 3 * block - 1, 2), "an import across a block boundary was not excluded");
require(!coverage.Covers(base + 2 * block, block) && !coverage.Covers(base + 3 * block, block), "a boundary import left one block watched");
require(coverage.Covers(base, block) && coverage.Covers(base + 4 * block, 1), "a boundary import excluded adjacent blocks");
require(!coverage.Exclude(base - block, block) && !coverage.Exclude(base + 5 * block, block), "an outside import changed coverage");
require(coverage.Exclude(base + 4 * block, std::numeric_limits<std::size_t>::max()), "the last partial block was not excluded");
require(!coverage.Covers(base + 4 * block, 1), "the last partial block is still watched");
coverage.Initialize(base, 2 * block);
require(coverage.Exclude(base - 1, 2) && !coverage.Covers(base, block), "an import crossing the arena start was not clipped");
require(coverage.Covers(base + block, block), "clipping an import excluded the rest of the arena");
coverage.Initialize(base, 4 * block + 1);
coverage.Exclude(base, 4 * block + 1);
require(!coverage.Restore(base + 1, block - 1) && !coverage.Covers(base, block), "a partial replacement restored an excluded block");
require(coverage.Restore(base + block, block) && coverage.Covers(base + block, block), "fresh private backing stayed excluded");
require(!coverage.Restore(base + block, block), "restoring an already watched block reports a change");
require(!coverage.Covers(base, block) && !coverage.Covers(base + 2 * block, block), "restoring one block restored its neighbours");
require(coverage.Restore(base + block - 1, 2 * block + 2), "a full interior replacement was not restored");
require(!coverage.Covers(base, block) && coverage.Covers(base + block, 2 * block) && !coverage.Covers(base + 3 * block, block), "a replacement restored partially covered boundary blocks");
require(!coverage.Restore(base - block, block) && !coverage.Restore(base + 5 * block, block), "an outside replacement changed coverage");
require(coverage.Restore(base + 3 * block, std::numeric_limits<std::size_t>::max()) && coverage.Covers(base + 3 * block, block + 1), "an oversized replacement was not clipped to the arena");
require(coverage.Restore(base - block, 2 * block) && coverage.Covers(base, block), "a replacement across the arena start was not clipped");
coverage.Exclude(base + block + 4096, 1);
require(!coverage.Covers(base + block, block), "a re-import did not exclude fresh backing again");
coverage.Initialize(base, 2 * block);
coverage.Exclude(base, block, 4);
require(!coverage.Restore(base, block, 3) && !coverage.Restore(base, block, 4), "a stale mapping notification restored a newer import");
require(coverage.Restore(base, block, 5) && coverage.Covers(base, block), "a newer private mapping did not restore an older exclusion");
coverage.Exclude(base, block, 5);
coverage.Exclude(base, block, 7);
require(!coverage.Restore(base, block, 6) && !coverage.Covers(base, block), "a delayed mapping notification erased a repeated import");
require(coverage.Restore(base, block, 8), "a fresh replacement did not restore the latest import exclusion");
std::cout << "write watch coverage passed\n";
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}