Files
AnyPS5/core/libs/tests/GuestJxrBitIo.cpp
T

325 lines
16 KiB
C++

#include "prx/libSceJxr/include/Api.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <memory>
#include <stdexcept>
#include <vector>
extern "C" std::uint32_t JxrBitIoReference_nid_no_patch(int operation, void* state, std::uint32_t value, std::uint32_t bits);
namespace {
constexpr std::size_t RingAlignment = 16384;
constexpr std::int32_t WriteMask = ~8192;
constexpr std::int32_t ReadMask = WriteMask & ~1;
void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "JXR bit I/O: %s\n", message);
std::abort();
}
template<typename TAction>
void RequireInvalidArgument(TAction action, const char* message) {
try {
action();
} catch (const std::invalid_argument&) {
return;
}
Require(false, message);
}
struct Ring {
std::vector<std::uint8_t> storage = std::vector<std::uint8_t>(RingAlignment * 3);
std::uint8_t* base = reinterpret_cast<std::uint8_t*>(
(reinterpret_cast<std::uintptr_t>(storage.data()) + RingAlignment - 1) & ~std::uintptr_t{RingAlignment - 1});
};
std::uint32_t seed = 0x2468ace1;
std::uint32_t Next() {
seed = seed * 1664525u + 1013904223u;
return seed >> 8;
}
std::uint32_t LoadBigEndian(const std::uint8_t* pointer) {
return (std::uint32_t{pointer[0]} << 24) | (std::uint32_t{pointer[1]} << 16) | (std::uint32_t{pointer[2]} << 8) | pointer[3];
}
Jxr::BitIo Attach(Ring& ring, std::size_t offset, std::uint32_t bitsUsed, std::size_t reference) {
Jxr::BitIo io{};
io.mask = ReadMask;
io.start = ring.base;
io.current = ring.base + offset;
io.bitsUsed = bitsUsed;
io.accumulator = LoadBigEndian(io.current) << bitsUsed;
io.referenceOffset = reference;
return io;
}
void RequireSame(const Jxr::BitIo& guest, const Ring& guestRing, const Jxr::BitIo& native, const Ring& nativeRing,
const char* message) {
Require(guest.accumulator == native.accumulator && guest.bitsUsed == native.bitsUsed && guest.mask == native.mask &&
guest.current - guestRing.base == native.current - nativeRing.base &&
guest.start - guestRing.base == native.start - nativeRing.base &&
guest.referenceOffset == native.referenceOffset,
message);
Require(std::memcmp(guestRing.base, nativeRing.base, RingAlignment) == 0, message);
}
std::uint32_t Guest(int operation, Jxr::BitIo* io, std::uint32_t value, std::uint32_t bits) {
switch (operation) {
case 0: return peekBit16_nid_postfix(io, bits);
case 1: return flushBit16_nid_postfix(io, bits);
case 2: return getBit16_nid_postfix(io, bits);
case 3: return getBool16_nid_postfix(io);
case 4: return static_cast<std::uint32_t>(getBit16s_nid_postfix(io, bits));
case 5: return getBit32_nid_postfix(io, bits);
case 6: return flushToByte_nid_postfix(io);
case 7: return getSizeRead_nid_postfix(io);
case 8: return getPosRead_nid_postfix(io);
case 9: putBit16z_nid_postfix(io, value, bits); return 0;
case 10: putBit16_nid_postfix(io, value, bits); return 0;
case 11: putBit32_nid_postfix(io, value, bits); return 0;
case 12: fillToByte_nid_postfix(io); return 0;
case 13: return getSizeWrite_nid_postfix(io);
default: return 0xffffffffu;
}
}
Jxr::BitIo* attachingIo = nullptr;
Jxr::Stream* attachingStream = nullptr;
std::int64_t positionResult = 0;
bool storePosition = true;
bool throwPosition = false;
int positionCalls = 0;
std::int64_t APS5_VABI AttachmentPosition(Jxr::Stream* stream, std::size_t* position) {
Require(stream == attachingStream, "attachment forwards original stream");
Require(position == &attachingIo->referenceOffset, "attachment forwards original offset field");
Require(attachingIo->bitsUsed == 13 && attachingIo->mask == 37,
"position callback precedes bit-state initialization");
++positionCalls;
if (throwPosition) throw std::invalid_argument("position callback failure");
if (storePosition) *position = std::numeric_limits<std::size_t>::max() - 17;
attachingIo->shadow = 0x76543210;
return positionResult;
}
void RunAttachWrite() {
alignas(Jxr::BitIo) std::array<std::uint8_t, 8192 + sizeof(Jxr::BitIo) + 16> storage;
storage.fill(0xa5);
auto* io = std::construct_at(reinterpret_cast<Jxr::BitIo*>(storage.data() + 8192));
Jxr::Stream stream{};
stream.getPosition = &AttachmentPosition;
attachingIo = io;
attachingStream = &stream;
for (const auto result : {std::int64_t{0}, std::int64_t{1} << 40, -(std::int64_t{1} << 40)}) {
for (const bool store : {false, true}) {
std::memset(io, 0x5c, sizeof(*io));
io->bitsUsed = 13;
io->mask = 37;
io->referenceOffset = 12345;
positionResult = result;
storePosition = store;
positionCalls = 0;
Require(attachISWrite_nid_postfix(io, &stream) == 0, "attachment ignores position callback result");
Require(positionCalls == 1, "attachment queries position once");
Require(io->referenceOffset == (store ? std::numeric_limits<std::size_t>::max() - 17 : 12345),
"attachment preserves callback offset effect");
Require(io->shadow == 0x76543210, "attachment preserves untouched shadow field");
Require(io->start == storage.data() && io->current == storage.data(), "attachment locates preceding buffer");
Require(io->accumulator == 0 && io->bitsUsed == 0 && io->mask == WriteMask && io->stream == &stream,
"attachment initializes writer state");
for (std::size_t i = 0; i < 8192; ++i) Require(storage[i] == 0xa5, "attachment keeps buffer bytes");
for (std::size_t i = 8192 + sizeof(*io); i < storage.size(); ++i)
Require(storage[i] == 0xa5, "attachment keeps trailing guard bytes");
}
}
io->bitsUsed = 13;
io->mask = 37;
std::array<std::byte, sizeof(*io)> before;
std::memcpy(before.data(), io, sizeof(*io));
throwPosition = true;
RequireInvalidArgument([&] { attachISWrite_nid_postfix(io, &stream); }, "attachment propagates callback exceptions");
Require(std::memcmp(before.data(), io, sizeof(*io)) == 0, "throwing position callback prevents initialization");
throwPosition = false;
stream.getPosition = nullptr;
RequireInvalidArgument([&] { attachISWrite_nid_postfix(io, &stream); }, "attachment needs a position callback");
RequireInvalidArgument([&] { attachISWrite_nid_postfix(io, nullptr); }, "attachment needs a stream");
RequireInvalidArgument([&] { attachISWrite_nid_postfix(nullptr, &stream); }, "attachment needs bit state");
std::destroy_at(io);
}
void RunReads() {
Ring guestRing, nativeRing;
for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast<std::uint8_t>(Next());
for (int round = 0; round < 64; ++round) {
const auto offset = (Next() % 4096) * 2;
const auto used = Next() % 16;
const std::size_t reference = 100000 + Next() % 100000;
auto guest = Attach(guestRing, offset, used, reference);
auto native = Attach(nativeRing, offset, used, reference);
for (int step = 0; step < 2000; ++step) {
const int operation = static_cast<int>(Next() % 9);
std::uint32_t bits = Next() % 17;
if (operation == 4) bits %= 16;
if (operation == 5) bits = Next() % 33;
const bool zeroBits = bits == 0 && (operation == 0 || operation == 2 || operation == 5);
const auto expected = zeroBits ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, 0, bits);
if (zeroBits && operation != 0) JxrBitIoReference_nid_no_patch(1, &native, 0, 0);
const auto actual = Guest(operation, &guest, 0, bits);
Require(actual == expected, "read result differs from jxrlib");
RequireSame(guest, guestRing, native, nativeRing, "read state differs from jxrlib");
}
}
}
void RunWrites() {
Ring guestRing, nativeRing;
for (int round = 0; round < 64; ++round) {
for (std::size_t i = 0; i < RingAlignment; ++i) guestRing.base[i] = nativeRing.base[i] = static_cast<std::uint8_t>(Next());
const auto offset = (Next() % 4096) * 2;
const auto used = Next() % 16;
const auto accumulator = Next() & (used == 0 ? 0u : 0xffffffffu >> (32 - used));
auto guest = Attach(guestRing, offset, used, 0);
guest.start = guestRing.base + (Next() % 2) * 4096;
guest.accumulator = accumulator;
guest.mask = WriteMask;
auto native = guest;
native.start = nativeRing.base + (guest.start - guestRing.base);
native.current = nativeRing.base + offset;
for (int step = 0; step < 2000; ++step) {
const int operation = 9 + static_cast<int>(Next() % 5);
std::uint32_t bits = operation == 11 ? Next() % 33 : Next() % 17;
std::uint32_t value = Next() ^ (Next() << 24);
if (operation == 9) value &= bits == 0 ? 0u : 0xffffffffu >> (32 - bits);
const bool emptyStore = native.bitsUsed == 0 && (operation == 12 || (operation != 13 && bits == 0));
const auto expected = emptyStore ? 0u : JxrBitIoReference_nid_no_patch(operation, &native, value, bits);
const auto actual = Guest(operation, &guest, value, bits);
Require(actual == expected, "write result differs from jxrlib");
RequireSame(guest, guestRing, native, nativeRing, "write state differs from jxrlib");
}
}
}
void RunZeroBits(Ring& ring) {
for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast<std::uint8_t>(i * 13 + 5);
for (std::uint32_t used = 0; used < 16; ++used) {
auto io = Attach(ring, 64, used, 0);
const auto before = io;
Require(peekBit16_nid_postfix(&io, 0) == 0, "zero-bit peek");
Require(std::memcmp(&io, &before, sizeof(io)) == 0, "zero-bit peek keeps the state");
Require(getBit16_nid_postfix(&io, 0) == 0, "zero-bit read");
Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator,
"zero-bit read keeps the position");
Require(getBit32_nid_postfix(&io, 0) == 0, "zero-bit 32-bit read");
Require(io.current == before.current && io.bitsUsed == used && io.accumulator == before.accumulator,
"zero-bit 32-bit read keeps the position");
}
std::vector<std::uint8_t> snapshot(ring.base, ring.base + RingAlignment);
Jxr::BitIo io{};
io.mask = WriteMask;
io.start = ring.base;
io.current = ring.base + 128;
const auto before = io;
putBit16z_nid_postfix(&io, 0, 0);
putBit16_nid_postfix(&io, 0xffffffffu, 0);
putBit32_nid_postfix(&io, 0xffffffffu, 0);
fillToByte_nid_postfix(&io);
Require(std::memcmp(&io, &before, sizeof(io)) == 0, "aligned empty stores keep the state");
Require(std::memcmp(snapshot.data(), ring.base, RingAlignment) == 0, "aligned empty stores keep the buffer");
putBit16_nid_postfix(&io, 0xfff5u, 4);
Require(io.accumulator == 5 && io.bitsUsed == 4 && io.current == ring.base + 128, "masked 4-bit store state");
Require(ring.base[128] == 0x50 && ring.base[129] == 0x00, "masked 4-bit store bytes");
fillToByte_nid_postfix(&io);
Require(io.bitsUsed == 8 && ring.base[128] == 0x50, "fill to the byte boundary");
fillToByte_nid_postfix(&io);
putBit16_nid_postfix(&io, 0x1234u, 0);
Require(io.bitsUsed == 8 && io.current == ring.base + 128, "empty stores at a byte boundary");
putBit32_nid_postfix(&io, 0xfabcdeu, 20);
Require(io.bitsUsed == 12 && io.current == ring.base + 130, "20-bit store position");
Require(ring.base[128] == 0x50 && ring.base[129] == 0xab && ring.base[130] == 0xcd && ring.base[131] == 0xe0,
"20-bit store bytes");
putBit32_nid_postfix(&io, 0xffffffffu, 32);
Require(io.bitsUsed == 12 && io.current == ring.base + 134, "32-bit store position");
Require(ring.base[130] == 0xcd && ring.base[131] == 0xef && ring.base[132] == 0xff && ring.base[133] == 0xff,
"32-bit store bytes");
}
void RunEdges() {
for (std::uint32_t qp = 0; qp < 256; ++qp)
Require(dquantBits_nid_postfix(static_cast<std::uint8_t>(qp)) ==
JxrBitIoReference_nid_no_patch(14, nullptr, qp, 0),
"dquantBits differs from jxrlib");
Ring ring;
for (std::size_t i = 0; i < RingAlignment; ++i) ring.base[i] = static_cast<std::uint8_t>(i * 7);
auto io = Attach(ring, 8190, 4, 0);
Require(getBit16_nid_postfix(&io, 12) == (LoadBigEndian(ring.base + 8190) << 4 >> 20), "wrap read value");
Require(io.current == ring.base && io.bitsUsed == 0, "read wraps to the ring start");
auto copy = Attach(ring, 10, 3, 0);
Require(getBit16_S_nid_postfix(nullptr, &copy, 5) == ((LoadBigEndian(ring.base + 10) << 3) >> 27), "getBit16_S value");
Require(copy.current == ring.base + 10 && copy.bitsUsed == 8, "getBit16_S state");
auto crossing = Attach(ring, 4096, 0, 0);
RequireInvalidArgument([&] { getBit16_S_nid_postfix(nullptr, &crossing, 1); }, "getBit16_S packet without stream");
RunZeroBits(ring);
for (const std::uint32_t used : {0u, 1u, 8u, 15u}) {
auto odd = Attach(ring, 129, used, 0);
odd.mask = WriteMask;
const auto before = odd;
const std::vector<std::uint8_t> snapshot(ring.base, ring.base + RingAlignment);
RequireInvalidArgument([&] { putBit16z_nid_postfix(&odd, 0, 0); }, "odd-position unmasked store");
RequireInvalidArgument([&] { putBit16_nid_postfix(&odd, 1, 1); }, "odd-position masked store");
RequireInvalidArgument([&] { putBit32_nid_postfix(&odd, 0xffffffffu, 32); }, "odd-position 32-bit store");
RequireInvalidArgument([&] { fillToByte_nid_postfix(&odd); }, "odd-position byte fill");
Require(std::memcmp(&odd, &before, sizeof(odd)) == 0, "rejected odd-position stores keep the state");
Require(std::memcmp(ring.base, snapshot.data(), RingAlignment) == 0, "rejected odd-position stores keep the buffer");
}
RequireInvalidArgument([] { getBit16_nid_postfix(nullptr, 1); }, "null read state");
RequireInvalidArgument([] { putBit16_nid_postfix(nullptr, 1, 1); }, "null write state");
RequireInvalidArgument([] { getSizeRead_nid_postfix(nullptr); }, "null size state");
RequireInvalidArgument([] { getSizeWrite_nid_postfix(nullptr); }, "null size write state");
RequireInvalidArgument([] { getPosRead_nid_postfix(nullptr); }, "null position state");
RequireInvalidArgument([] { flushToByte_nid_postfix(nullptr); }, "null flush state");
RequireInvalidArgument([] { fillToByte_nid_postfix(nullptr); }, "null fill state");
auto edge = Attach(ring, 0, 0, 0);
RequireInvalidArgument([&] { peekBit16_nid_postfix(&edge, 17); }, "17-bit peek");
RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 17); }, "17-bit flush");
RequireInvalidArgument([&] { getBit16s_nid_postfix(&edge, 16); }, "16-bit signed read");
RequireInvalidArgument([&] { getBit32_nid_postfix(&edge, 33); }, "33-bit read");
RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 2, 1); }, "wide unmasked value");
RequireInvalidArgument([&] { putBit16_nid_postfix(&edge, 0, 17); }, "17-bit write");
RequireInvalidArgument([&] { putBit32_nid_postfix(&edge, 0, 33); }, "33-bit write");
edge.mask = WriteMask;
edge.bitsUsed = 16;
RequireInvalidArgument([&] { putBit16z_nid_postfix(&edge, 0, 0); }, "write position past the word");
RequireInvalidArgument([&] { fillToByte_nid_postfix(&edge); }, "fill position past the word");
edge.bitsUsed = 0;
edge.mask = -1;
RequireInvalidArgument([&] { flushBit16_nid_postfix(&edge, 1); }, "odd wrap mask");
edge.mask = ReadMask;
edge.current = nullptr;
RequireInvalidArgument([&] { getBit16_nid_postfix(&edge, 1); }, "null current pointer");
}
} // namespace
int main() {
RunAttachWrite();
RunReads();
RunWrites();
RunEdges();
return 0;
}