AnyPS5/core/libs/tests/GuestRandomState.cpp

190 lines
9.2 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <thread>
#include <vector>
extern "C" {
std::int64_t APS5_VABI random_nid_postfix();
void APS5_VABI srandom_nid_postfix(std::uint64_t);
char* APS5_VABI initstate_nid_postfix(std::uint64_t, char*, std::int64_t);
char* APS5_VABI setstate_nid_postfix(char*);
int* APS5_VABI __error_nid_postfix();
double APS5_VABI erand48_nid_postfix(std::uint16_t*);
std::int64_t APS5_VABI nrand48_nid_postfix(std::uint16_t*);
std::int64_t APS5_VABI jrand48_nid_postfix(std::uint16_t*);
}
static int failures = 0;
static void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "%s\n", message);
++failures;
}
}
using Sequence = std::array<std::int64_t, 5>;
static Sequence Draw() {
Sequence values{};
for (auto& value : values) value = random_nid_postfix();
return values;
}
static std::uint32_t Word(const char* table, std::size_t index) {
std::uint32_t value;
std::memcpy(&value, table + sizeof(value) * index, sizeof(value));
return value;
}
static bool SeedIs(const std::uint16_t* seed, std::uint16_t low, std::uint16_t middle, std::uint16_t high) {
return seed[0] == low && seed[1] == middle && seed[2] == high;
}
static void CheckRand48() {
std::uint16_t seed[3] = {0x330e, 0xabcd, 0x1234};
Require(erand48_nid_postfix(seed) == 0x657eb7255101p-48 && SeedIs(seed, 0x5101, 0xb725, 0x657e), "erand48 first value");
Require(nrand48_nid_postfix(seed) == 1804928587 && SeedIs(seed, 0x6378, 0x0c96, 0xd72a), "nrand48 second value");
Require(jrand48_nid_postfix(seed) == 1517566982 && SeedIs(seed, 0x2a23, 0x3c06, 0x5a74), "jrand48 third value");
Require(erand48_nid_postfix(seed) == 0x72534abf62f2p-48 && SeedIs(seed, 0x62f2, 0x4abf, 0x7253), "erand48 fourth value");
Require(nrand48_nid_postfix(seed) == 684387517 && SeedIs(seed, 0x8d15, 0xd97a, 0x5195), "nrand48 fifth value");
Require(jrand48_nid_postfix(seed) == -487786166 && SeedIs(seed, 0xeffc, 0xf94a, 0xe2ec), "jrand48 negative value");
std::uint16_t zero[3] = {};
Require(nrand48_nid_postfix(zero) == 0 && SeedIs(zero, 0x000b, 0, 0), "rand48 zero seed adds the increment");
std::uint16_t top[3] = {0xffff, 0xffff, 0xffff};
const double value = erand48_nid_postfix(top);
Require(value >= 0.0 && value < 1.0, "erand48 stays below 1");
#ifndef _WIN32
std::uint16_t guest[3] = {0x1234, 0x5678, 0x9abc};
unsigned short host[3] = {0x1234, 0x5678, 0x9abc};
for (int round = 0; round < 64; ++round) {
Require(erand48_nid_postfix(guest) == ::erand48(host), "erand48 matches the host");
Require(nrand48_nid_postfix(guest) == ::nrand48(host), "nrand48 matches the host");
Require(jrand48_nid_postfix(guest) == ::jrand48(host), "jrand48 matches the host");
}
#endif
}
int main() {
CheckRand48();
constexpr Sequence seedOne {1408992891, 1638182910, 83848093, 145962118, 627851256};
constexpr Sequence seed12345 {1052984060, 1639322404, 1971525204, 2097956667, 2012147915};
constexpr std::array<Sequence, 5> seed42ByType {{
{2060028338, 1164936638, 481681973, 1763071474, 962919018},
{660273387, 1356916325, 287587791, 1132382070, 1497594867},
{1015209508, 12952757, 1418769128, 2005808989, 1212998434},
{745791173, 418148062, 949332769, 1480536337, 177711995},
{996638292, 1209745284, 1148231710, 400392963, 1149215164},
}};
constexpr std::array<std::int64_t, 6> seed7Type1 {1271472800, 27686551, 1252069488, 1609001632, 28873941, 508017694};
Require(Draw() == seedOne, "unseeded random must match srandom(1)");
srandom_nid_postfix(1);
Require(Draw() == seedOne, "srandom(1) sequence");
srandom_nid_postfix(0x100000001ull);
Require(Draw() == seedOne, "srandom must use the low 32 bits of the seed");
srandom_nid_postfix(12345);
Require(Draw() == seed12345, "srandom(12345) sequence");
struct SizeCase {
std::int64_t size;
int type;
std::size_t usedBytes;
};
constexpr std::array<SizeCase, 10> sizes {{
{8, 0, 8}, {31, 0, 8}, {32, 1, 32}, {63, 1, 32}, {64, 2, 64},
{127, 2, 64}, {128, 3, 128}, {255, 3, 128}, {256, 4, 256}, {1000, 4, 256},
}};
std::vector<std::vector<char>> buffers;
char* defaultTable = nullptr;
char* active = nullptr;
for (const auto& sizeCase : sizes) {
auto& buffer = buffers.emplace_back(static_cast<std::size_t>(sizeCase.size) + 4, static_cast<char>(0xa5));
char* previous = initstate_nid_postfix(42, buffer.data(), sizeCase.size);
if (defaultTable == nullptr) defaultTable = previous;
else Require(previous == active, "initstate must return the previous state");
Require(Word(buffer.data(), 0) == static_cast<std::uint32_t>(sizeCase.type), "initstate type word");
Require(Draw() == seed42ByType[static_cast<std::size_t>(sizeCase.type)], "initstate(42) sequence for its size");
Require(std::all_of(buffer.begin() + static_cast<std::ptrdiff_t>(sizeCase.usedBytes), buffer.end(),
[](char value) { return value == static_cast<char>(0xa5); }), "initstate must use only the rounded state size");
active = buffer.data();
}
Require(defaultTable != nullptr && Word(defaultTable, 0) == 5 * 5 + 3, "default table must hold its rear position");
Require(setstate_nid_postfix(defaultTable) == active, "setstate must return the previous state");
Require(random_nid_postfix() == 1533042628 && random_nid_postfix() == 1225903521,
"setstate on the default table must continue the srandom(12345) sequence");
alignas(4) std::array<char, 32> small{};
alignas(4) std::array<char, 64> large{};
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
for (std::size_t i = 0; i < 3; ++i) Require(random_nid_postfix() == seed7Type1[i], "initstate(7) on 32 bytes");
initstate_nid_postfix(7, large.data(), static_cast<std::int64_t>(large.size()));
Require(setstate_nid_postfix(small.data()) == large.data(), "setstate must return the 64-byte state");
Require(setstate_nid_postfix(large.data()) == small.data(), "setstate must return the 32-byte state");
Require(Word(small.data(), 0) == 5 * 3 + 1, "setstate must save the rear position of the old state");
Require(setstate_nid_postfix(small.data()) == large.data(), "setstate back to the 32-byte state");
for (std::size_t i = 3; i < seed7Type1.size(); ++i) Require(random_nid_postfix() == seed7Type1[i], "setstate must resume the sequence");
initstate_nid_postfix(7, small.data(), static_cast<std::int64_t>(small.size()));
random_nid_postfix();
alignas(4) std::array<char, 32> garbled{};
const std::uint32_t garbledWord = 5 * 7 + 1;
std::memcpy(garbled.data(), &garbledWord, sizeof(garbledWord));
Require(setstate_nid_postfix(garbled.data()) == nullptr, "setstate must reject a rear position past the degree");
Require(random_nid_postfix() == seed7Type1[1], "a rejected setstate must keep the current state");
*__error_nid_postfix() = 1234;
std::array<char, 8> tiny{};
tiny.fill(static_cast<char>(0x5a));
for (const std::int64_t size : {std::int64_t{7}, std::int64_t{0}, std::int64_t{-1}})
Require(initstate_nid_postfix(1, tiny.data(), size) == nullptr, "initstate below 8 bytes must return null");
Require(initstate_nid_postfix(1, nullptr, 4) == nullptr, "initstate below 8 bytes must return null before reading the state");
Require(*__error_nid_postfix() == 1234, "initstate below 8 bytes must leave errno unchanged");
Require(std::all_of(tiny.begin(), tiny.end(), [](char value) { return value == static_cast<char>(0x5a); }),
"initstate below 8 bytes must not write the state");
Require(random_nid_postfix() == seed7Type1[2], "a rejected initstate must keep the current state");
bool threw = false;
try {
initstate_nid_postfix(1, nullptr, 128);
} catch (const std::invalid_argument&) {
threw = true;
}
Require(threw, "initstate with a null state must throw");
threw = false;
try {
setstate_nid_postfix(nullptr);
} catch (const std::invalid_argument&) {
threw = true;
}
Require(threw, "setstate with a null state must throw");
constexpr std::size_t threadCount = 4;
constexpr std::size_t callsPerThread = 2000;
srandom_nid_postfix(99);
std::vector<std::int64_t> serial(threadCount * callsPerThread);
for (auto& value : serial) value = random_nid_postfix();
srandom_nid_postfix(99);
std::vector<std::int64_t> concurrent(threadCount * callsPerThread);
std::vector<std::thread> threads;
for (std::size_t t = 0; t < threadCount; ++t)
threads.emplace_back([&concurrent, t] {
for (std::size_t i = 0; i < callsPerThread; ++i) concurrent[t * callsPerThread + i] = random_nid_postfix();
});
for (auto& thread : threads) thread.join();
std::sort(serial.begin(), serial.end());
std::sort(concurrent.begin(), concurrent.end());
Require(serial == concurrent, "concurrent calls must draw the serial sequence");
return failures == 0 ? 0 : 1;
}