Files
psxrecomp/runtime/tests/test_mod_immediate.c
Matt Stanley 13b7f81d29 Add guarded instruction variants fixed during offline mod activation
Keep selector zero stock and seal all choices until the next session restart. Refuse nonzero online activation, scope non-stock save states by frozen choices, and preserve original instruction guards when combined with effective-address adapters. Use overlay ABI34 on current master without the old menu dependency stack.
2026-10-19 16:37:32 -07:00

189 lines
8.8 KiB
C

/* [[mod_immediate.sites]] runtime store, selectors and the interpreter's word
* substitution (mod_immediate.c): physical addresses, (address, word) lookup,
* sorting, the cap and its log line, invalid entries dropped, empty and NULL
* lists, selector range checks, the live gate and session reset, and
* psx_mod_immediate_patch swapping exactly the imm16 of a listed word.
* The interpreter call site is pinned by test_interp_mod_immediate_guards.py. */
#include "../src/mod_immediate.c"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32)
#include <io.h>
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#else
#include <unistd.h>
#endif
static int failures;
#define CHECK(cond, ...) \
do { \
if (!(cond)) { \
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fputc('\n', stderr); \
++failures; \
} \
} while (0)
#define SLTI_V0_V0_20 0x28420020u /* slti v0, v0, 0x20 */
#define SLTIU_T2_A1_20 0x2CAA0020u /* sltiu t2, a1, 0x20 */
#define LBU_V0_V1_2E9 0x906202E9u /* lbu v0, 0x2e9(v1) */
static const char *s_capture_path = "mod_immediate_unit_stderr.txt";
static void capture_stderr(void (*fn)(void), char *buf, size_t cap) {
FILE *tmp = fopen(s_capture_path, "w+b");
buf[0] = '\0';
if (!tmp) {
CHECK(0, "cannot open the stderr capture file %s", s_capture_path);
fn();
return;
}
fflush(stderr);
int saved = dup(fileno(stderr));
dup2(fileno(tmp), fileno(stderr));
fn();
fflush(stderr);
dup2(saved, fileno(stderr));
close(saved);
rewind(tmp);
size_t n = fread(buf, 1, cap - 1, tmp);
buf[n] = '\0';
fclose(tmp);
remove(s_capture_path);
}
/* ---- store ----------------------------------------------------------------- */
#define BIG_N (PSX_MOD_IMMEDIATE_SITES_MAX + 44)
static PSXModImmediateSite big[BIG_N];
static void set_big(void) { (void)psx_mod_immediate_set_sites(big, BIG_N); }
static PSXModImmediateSite mk(uint32_t addr, uint32_t word, uint32_t group, uint32_t n,
uint16_t a, uint16_t b) {
PSXModImmediateSite s;
memset(&s, 0, sizeof s);
s.address = addr; s.expected = word; s.group = group; s.count = n;
s.values[0] = a; s.values[1] = b;
return s;
}
static void test_store(void) {
const PSXModImmediateSite sites[] = {
mk(0x80020010u, SLTI_V0_V0_20, 1, 2, 0x40, 0x80),
mk(0xA0020000u, LBU_V0_V1_2E9, 2, 1, 0x3E9, 0), /* KSEG1 spelling */
mk(0x00020010u, SLTIU_T2_A1_20, 1, 2, 0x40, 0x80), /* same address, other word */
mk(0x80020020u, SLTI_V0_V0_20, 64, 1, 1, 0), /* bad group: dropped */
mk(0x80020030u, SLTI_V0_V0_20, 1, 0, 0, 0), /* no values: dropped */
};
CHECK(psx_mod_immediate_set_sites(sites, 5) == 3, "three valid sites stored");
CHECK(psx_mod_immediate_site_count() == 3, "count 3");
CHECK(s_imm_sites[0].address == 0x00020000u && s_imm_sites[1].address == 0x00020010u &&
s_imm_sites[2].address == 0x00020010u && s_imm_sites[1].expected < s_imm_sites[2].expected,
"stored sorted by (physical address, word)");
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
(void)psx_mod_set_immediate_selector(1, 1);
CHECK(psx_mod_immediate_patch(0x80020010u, SLTI_V0_V0_20) == 0x28420040u,
"KSEG0 lookup, selector 1 -> 0x40");
CHECK(psx_mod_immediate_patch(0x00020010u, SLTIU_T2_A1_20) == 0x2CAA0040u,
"the other word at the shared address is its own site");
CHECK(psx_mod_immediate_patch(0x80020010u, LBU_V0_V1_2E9) == LBU_V0_V1_2E9,
"an unlisted word at a listed address is unchanged");
CHECK(psx_mod_immediate_patch(0x80020000u, LBU_V0_V1_2E9) == LBU_V0_V1_2E9,
"group 2 is still selector 0");
(void)psx_mod_set_immediate_selector(2, 1);
CHECK(psx_mod_immediate_patch(0x80020000u, LBU_V0_V1_2E9) == 0x906203E9u,
"KSEG1 entry found through KSEG0, imm16 swapped");
CHECK(psx_mod_immediate_patch(0x80020004u, LBU_V0_V1_2E9) == LBU_V0_V1_2E9,
"between entries is unchanged");
CHECK(psx_mod_immediate_set_sites(NULL, 5) == 0, "NULL list stores none");
CHECK(psx_mod_immediate_set_sites(sites, -1) == 0, "negative count stores none");
CHECK(psx_mod_immediate_patch(0x80020010u, SLTI_V0_V0_20) == SLTI_V0_V0_20,
"empty store is unchanged");
for (int i = 0; i < BIG_N; i++)
big[i] = mk(0x80040000u + 4u * (uint32_t)i, SLTI_V0_V0_20, 3, 1, 0x40, 0);
char log[512];
capture_stderr(set_big, log, sizeof log);
CHECK(psx_mod_immediate_site_count() == PSX_MOD_IMMEDIATE_SITES_MAX,
"over-long list stored %d", psx_mod_immediate_site_count());
CHECK(strstr(log, "keeping the first 4096") != NULL, "overflow not logged (got \"%s\")", log);
(void)psx_mod_immediate_set_sites(NULL, 0);
}
/* ---- selectors ------------------------------------------------------------- */
static void test_selectors(void) {
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
CHECK(psx_mod_set_immediate_selector(1, 1) == 0, "no sites: the setter says so");
CHECK(psx_mod_immediate_selector(1) == 1u, "the selector is still recorded");
CHECK(!g_psx_mod_immediate_live, "no sites: interpreter gate stays off");
const PSXModImmediateSite one = mk(0x80020000u, SLTI_V0_V0_20, 1, 2, 0x40, 0x80);
(void)psx_mod_immediate_set_sites(&one, 1);
CHECK(g_psx_mod_immediate_live, "sites with a nonzero selector: gate on");
CHECK(psx_mod_set_immediate_selector(1, 0) == 1, "sites: the setter says so");
CHECK(!g_psx_mod_immediate_live, "selector 0: gate off");
CHECK(psx_mod_set_immediate_selector(64, 1) == 0 && psx_mod_set_immediate_selector(1, 16) == 0,
"out-of-range group / selector refused");
CHECK(psx_mod_immediate_select(1) == 0u && psx_mod_immediate_select(99) == 0u,
"unset and out-of-range groups read 0");
(void)psx_mod_set_immediate_selector(1, 2);
CHECK(psx_mod_immediate_select(1) == 2u && g_psx_mod_immediate_live, "selector 2 live");
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
CHECK(psx_mod_immediate_select(1) == 0u && !g_psx_mod_immediate_live, "reset clears");
/* A selector past the site's value count runs stock. */
(void)psx_mod_set_immediate_selector(1, 5);
CHECK(psx_mod_immediate_patch(0x80020000u, SLTI_V0_V0_20) == SLTI_V0_V0_20,
"selector beyond the value count is stock");
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
(void)psx_mod_immediate_set_sites(NULL, 0);
}
static void test_session_boundary(void) {
const PSXModImmediateSite one = mk(0x80020000u, SLTI_V0_V0_20, 1, 2, 0x40, 0x80);
psx_mod_immediate_set_sites(&one, 1);
psx_mod_immediate_reset();
CHECK(!psx_mod_set_immediate_selector(1, 1), "closed before activation");
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
CHECK(psx_mod_set_immediate_selector(1, 2), "offline activation selects128");
psx_mod_immediate_end_activation();
CHECK(!psx_mod_set_immediate_selector(1, 1) && !psx_mod_set_immediate_selector(1, 0),
"both mutation and disabling require restart");
CHECK(psx_mod_immediate_select(1) == 2, "rejected changes retain128");
psx_mod_immediate_begin_session(1);
psx_mod_immediate_begin_activation();
CHECK(psx_mod_immediate_select(1) == 0 && !g_psx_mod_immediate_live, "online starts stock");
CHECK(!psx_mod_set_immediate_selector(1, 1), "online nonzero refused");
psx_mod_immediate_end_activation();
CHECK(!psx_mod_immediate_session_valid() && psx_mod_immediate_select(1) == 0,
"ignored online refusal still invalidates launch");
psx_mod_immediate_begin_session(0);
psx_mod_immediate_begin_activation();
CHECK(psx_mod_immediate_session_valid() && psx_mod_immediate_select(1) == 0, "next offline session reset");
CHECK(psx_mod_set_immediate_selector(1, 1), "new offline session can select64");
psx_mod_immediate_end_activation();
psx_mod_immediate_reset();
}
int main(int argc, char **argv) {
if (argc > 1) s_capture_path = argv[1];
test_store();
test_selectors();
test_session_boundary();
if (failures) {
fprintf(stderr, "%d check(s) failed\n", failures);
return 1;
}
puts("PASS mod immediate store, selectors and patch");
return 0;
}