mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 09:51:02 +00:00
The generator emits a guest poll such as `ldr w9, [x19]; cmp w9, #1; b.eq` as a plain C load, and clang hoists it out of the loop: the lock wait in f_7001fa0e2c compiled to `cmp; jne; jmp .`, so a thread that found the lock held spun forever after its holder released it. A fight run hung this way in 3 of 13 launches on an RX 9070, with the UBO snapshot on and off; gdb showed the thread on that `jmp` in every sample. The gate is an opaque call on every back-edge of the 868 loops tools/spin_waits.py lists, so each pass reloads memory: the exe goes from 182 self-jumps to 148, and 144 of those are the game's own `b .` instructions. 8 of 8 fight runs then passed; tests/diff/func.py with --patches passed 5470 cases on 878 functions, and the four probes passed on 1.4.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
5.9 KiB
C
163 lines
5.9 KiB
C
// Runs lifted tests and writes the resulting CPU state per case.
|
|
// Shared by tests/diff/lift.py, simd.py, atomic.py and func.py; the expected state comes from Unicorn on the Python
|
|
// side.
|
|
//
|
|
// argv: buf.bin cases.bin out.bin image base [image base ...]
|
|
// Each image is mapped at its guest address (guest == host), the scratch buffer at BUF.
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <sys/mman.h>
|
|
#endif
|
|
|
|
#include "cpu.h"
|
|
|
|
extern GuestFn tests[];
|
|
extern int ntests;
|
|
|
|
#define BUF 0x7200000000ull
|
|
#define BUF_SIZE 0x40000
|
|
|
|
#define PTR_LO (BUF + 0x8000)
|
|
#define PTR_SPAN 0x20000
|
|
|
|
static int g_stubs; // In.stubs of the running case
|
|
static uint64_t g_stub_h; // digest of every call's state so far
|
|
|
|
static uint64_t splitmix(uint64_t z) {
|
|
z += 0x9E3779B97F4A7C15ull;
|
|
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ull;
|
|
z = (z ^ (z >> 27)) * 0x94D049BB133111EBull;
|
|
return z ^ (z >> 31);
|
|
}
|
|
|
|
static uint64_t mix(uint64_t h, uint64_t w) { return (h ^ w) * 0x100000001B3ull; }
|
|
|
|
// The callee of every call in a function under tests/diff/func.py; Runner.stub there is the same function on the
|
|
// Unicorn side. Folds the target, X0-X30, SP and V0-V7 into a digest, so a register the caller left unwritten at the
|
|
// call changes it, then sets X0-X18, NZCV and V0-V7 from the digest, so a caller reading a register it held across
|
|
// the call sees a value the callee could have left. X0-X7 mostly get pointers into the scratch buffer. The digest
|
|
// and the call count go to the top of the scratch buffer, which the memory check covers.
|
|
static void rt_stub(Cpu *c, uint64_t target) {
|
|
uint64_t h = mix(g_stub_h, target);
|
|
for (int i = 0; i < 31; i++) h = mix(h, c->x[i]);
|
|
h = mix(h, c->sp);
|
|
for (int i = 0; i < 8; i++) h = mix(mix(h, (uint64_t)c->v[i]), (uint64_t)(c->v[i] >> 64));
|
|
g_stub_h = h;
|
|
for (int i = 0; i < 19; i++) {
|
|
uint64_t s = splitmix(h + i);
|
|
c->x[i] = i < 8 && (s & 3) ? PTR_LO + ((s >> 4) % (PTR_SPAN / 16)) * 16 : s;
|
|
}
|
|
c->nzcv = splitmix(h + 19) >> 60 << 28;
|
|
for (int i = 0; i < 8; i++)
|
|
c->v[i] = (__uint128_t)splitmix(h + 33 + 2 * i) << 64 | splitmix(h + 32 + 2 * i);
|
|
uint64_t *slot = (uint64_t *)(uintptr_t)(BUF + BUF_SIZE - 16);
|
|
slot[0]++;
|
|
slot[1] = h;
|
|
}
|
|
|
|
void rt_dispatch(Cpu *c) {
|
|
if (g_stubs) rt_stub(c, c->pc);
|
|
}
|
|
void rt_stub_call(Cpu *c, uint64_t target) { rt_stub(c, target); }
|
|
void rt_spin_gate(Cpu *c, uint64_t head) { (void)c; (void)head; }
|
|
void rt_svc(Cpu *c, uint32_t imm, uint64_t pc) { (void)c; (void)imm; (void)pc; }
|
|
void rt_brk(Cpu *c, uint32_t imm, uint64_t pc) { (void)c; (void)imm; (void)pc; abort(); }
|
|
void rt_udf(Cpu *c, uint64_t pc) { (void)c; (void)pc; abort(); }
|
|
uint64_t rt_ticks(void) { return 0; }
|
|
volatile int g_fnprof;
|
|
void fnprof_hit(uint64_t addr, uint64_t lr, uint64_t x0) { (void)addr; (void)lr; (void)x0; }
|
|
|
|
static void *map_at(uint64_t addr, size_t len) {
|
|
len = (len + 0xffff) & ~(size_t)0xffff;
|
|
#ifdef _WIN32
|
|
void *p = VirtualAlloc((void *)addr, len, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
|
#else
|
|
void *p = mmap((void *)addr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
|
#endif
|
|
if (p != (void *)addr) {
|
|
fprintf(stderr, "map %llx (+%zx) failed\n", (unsigned long long)addr, len);
|
|
exit(1);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static void *load_at(const char *path, uint64_t addr) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) { perror(path); exit(1); }
|
|
fseek(f, 0, SEEK_END);
|
|
size_t size = (size_t)ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
void *p = map_at(addr, size);
|
|
if (fread(p, 1, size, f) != size) { perror(path); exit(1); }
|
|
fclose(f);
|
|
return p;
|
|
}
|
|
|
|
static uint32_t crc_tab[256];
|
|
static uint32_t crc32_buf(const uint8_t *p, size_t n) {
|
|
uint32_t c = 0xffffffffu;
|
|
for (size_t i = 0; i < n; i++) c = crc_tab[(c ^ p[i]) & 0xff] ^ (c >> 8);
|
|
return c ^ 0xffffffffu;
|
|
}
|
|
|
|
// Layouts match IN_FMT / OUT_FMT in tests/diff/common.py.
|
|
struct In { uint64_t k, fpcr, x[32], sp, nzcv; __uint128_t v[32]; uint64_t memchk, stubs; };
|
|
struct Out { uint64_t x[32], sp, nzcv, pc, mem; __uint128_t v[32]; };
|
|
|
|
int main(int argc, char **argv) {
|
|
if (argc < 6 || (argc - 4) % 2) {
|
|
fprintf(stderr, "usage: %s buf.bin cases.bin out.bin image base [image base ...]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
for (uint32_t i = 0; i < 256; i++) {
|
|
uint32_t c = i;
|
|
for (int k = 0; k < 8; k++) c = c & 1 ? (c >> 1) ^ 0xEDB88320u : c >> 1;
|
|
crc_tab[i] = c;
|
|
}
|
|
for (int i = 4; i < argc; i += 2) load_at(argv[i], strtoull(argv[i + 1], 0, 16));
|
|
uint8_t *buf = map_at(BUF, BUF_SIZE);
|
|
FILE *bf = fopen(argv[1], "rb");
|
|
if (!bf || fread(buf, 1, BUF_SIZE, bf) != BUF_SIZE) { perror(argv[1]); return 1; }
|
|
fclose(bf);
|
|
uint8_t *pristine = malloc(BUF_SIZE);
|
|
memcpy(pristine, buf, BUF_SIZE);
|
|
|
|
FILE *f = fopen(argv[2], "rb"), *o = fopen(argv[3], "wb");
|
|
if (!f || !o) return 1;
|
|
static struct In in;
|
|
static struct Out out;
|
|
static Cpu c;
|
|
while (fread(&in, sizeof in, 1, f) == 1) {
|
|
if (in.k >= (uint64_t)ntests) { fprintf(stderr, "bad test index %llu\n", (unsigned long long)in.k); return 1; }
|
|
memset(&c, 0, sizeof c);
|
|
memcpy(c.x, in.x, sizeof c.x);
|
|
c.sp = in.sp;
|
|
c.nzcv = in.nzcv;
|
|
c.fpcr = in.fpcr;
|
|
rt_fpcr_sync(&c);
|
|
memcpy(c.v, in.v, sizeof c.v);
|
|
c.excl_addr = ~0ull;
|
|
g_stubs = (int)in.stubs;
|
|
g_stub_h = 0;
|
|
tests[in.k](&c);
|
|
memcpy(out.x, c.x, sizeof out.x);
|
|
out.sp = c.sp;
|
|
out.nzcv = c.nzcv & 0xf0000000ull;
|
|
out.pc = c.pc;
|
|
out.mem = 0;
|
|
if (in.memchk) {
|
|
out.mem = crc32_buf(buf, BUF_SIZE);
|
|
memcpy(buf, pristine, BUF_SIZE);
|
|
}
|
|
memcpy(out.v, c.v, sizeof out.v);
|
|
fwrite(&out, sizeof out, 1, o);
|
|
// Flushed per case so a crash leaves the completed cases readable.
|
|
fflush(o);
|
|
}
|
|
fclose(o);
|
|
return 0;
|
|
}
|