This commit is contained in:
vetusmagnus
2026-10-13 20:04:31 -04:00
commit 28916eaf7d
208 changed files with 23516 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
/* gs_gl_egl.c: an OpenGL 3.3 core context without a window for the GL
* renderer's test harness (tools/gs_replay.c built with GS_GL=1, 64-bit):
* EGL's surfaceless platform (Mesa, e.g. llvmpipe), loaded with dlopen so no
* GL or EGL headers or link libraries are needed. */
#include <dlfcn.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
typedef void *EGLDisplay, *EGLContext, *EGLConfig, *EGLSurface;
typedef int32_t EGLint;
typedef unsigned EGLBoolean, EGLenum;
static void *egl;
static void *(*eglGetProcAddress_)(const char *);
void *(*gl_proc_override)(const char *); /* the harness's stubs (REPLAY_GLNULL) */
void *plat_gl_proc(const char *name)
{
if (gl_proc_override) return gl_proc_override(name);
if (!egl) return 0;
void *p = eglGetProcAddress_ ? eglGetProcAddress_(name) : 0;
if (!p && egl) p = dlsym(egl, name);
return p;
}
static EGLBoolean (*egl_mc)(EGLDisplay, EGLSurface, EGLSurface, EGLContext);
static EGLDisplay egl_d; static EGLContext egl_c;
int egl_gl_init(void)
{
egl = dlopen("libEGL.so.1", RTLD_NOW);
if (!egl) { fprintf(stderr, "no libEGL: %s\n", dlerror()); return 0; }
eglGetProcAddress_ = (void *(*)(const char *))dlsym(egl, "eglGetProcAddress");
EGLDisplay (*getPlatformDisplay)(EGLenum, void *, const EGLint *) = (EGLDisplay (*)(EGLenum, void *, const EGLint *))eglGetProcAddress_("eglGetPlatformDisplayEXT");
EGLBoolean (*initialize)(EGLDisplay, EGLint *, EGLint *) = (EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *))dlsym(egl, "eglInitialize");
EGLBoolean (*bindAPI)(EGLenum) = (EGLBoolean (*)(EGLenum))dlsym(egl, "eglBindAPI");
EGLBoolean (*chooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *) = (EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *))dlsym(egl, "eglChooseConfig");
EGLContext (*createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *) = (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *))dlsym(egl, "eglCreateContext");
EGLBoolean (*makeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext) = (EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))dlsym(egl, "eglMakeCurrent");
if (!getPlatformDisplay || !initialize || !bindAPI || !chooseConfig || !createContext || !makeCurrent) { fprintf(stderr, "EGL entry points missing\n"); return 0; }
EGLDisplay d = getPlatformDisplay(0x31DD /* EGL_PLATFORM_SURFACELESS_MESA */, 0, 0);
EGLint ma, mi;
if (!d || !initialize(d, &ma, &mi)) { fprintf(stderr, "eglInitialize failed\n"); return 0; }
bindAPI(0x30A2 /* EGL_OPENGL_API */);
EGLint ca[] = { 0x3040 /* RENDERABLE_TYPE */, 0x0008 /* OPENGL_BIT */, 0x3038 };
EGLConfig cfg; EGLint n = 0;
if (!chooseConfig(d, ca, &cfg, 1, &n) || n < 1) cfg = 0; /* EGL_KHR_no_config_context */
EGLint xa[] = { 0x3098, 3, 0x30FB, 3, 0x30FD, 1 /* core */, 0x3038 };
EGLContext c = createContext(d, cfg, 0, xa);
if (!c) { fprintf(stderr, "eglCreateContext failed\n"); return 0; }
if (!makeCurrent(d, 0, 0, c)) { fprintf(stderr, "eglMakeCurrent failed\n"); return 0; }
egl_mc = makeCurrent; egl_d = d; egl_c = c;
return 1;
}
/* the context current on the calling thread, or released (the GL thread takes it over) */
void egl_gl_current(int on) { if (egl_mc && !egl_mc(egl_d, 0, 0, on ? egl_c : 0)) fprintf(stderr, "eglMakeCurrent(%d) failed\n", on); else if (getenv("REPLAY_GLDBG")) fprintf(stderr, "eglMakeCurrent(%d) ok\n", on); }
+205
View File
@@ -0,0 +1,205 @@
/* gs_replay.c: runs GS input recorded with UYA_GSREC (host/src/gs/gs.c)
* through a build of gs.c and prints a hash of GS memory and of the shown
* frame after every vsync, and the time the GS took. Two builds of gs.c that
* print the same hashes drew the same pixels.
*
* build (32-bit like the game; GS_C is the gs.c to test):
* zig cc -target x86-linux-musl -O2 -march=nehalem -mfpmath=sse -malign-double -fno-strict-aliasing -fwrapv \
* -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c host/src/gs/gs_gl.c -o gs_replay -static -lpthread
* (a gs.c elsewhere: an absolute path in GS_C and -I host/src/gs)
* run: gs_replay rec.bin [frames_dir] (UYA_GS_THREADS=n: drawing threads, default 0;
* REPLAY_QUIET=1: no GS log; REPLAY_PROF=file: a program counter histogram, as UYA_PROF)
*
* The GL renderer (host/src/gs/gs_gl.c) in a 64-bit build with an EGL
* context without a window (Mesa llvmpipe), frames read back from the GPU:
* gcc -O2 -DGS_GL -I host/include -I host/src/gs -DGS_C='"../host/src/gs/gs.c"' tools/gs_replay.c \
* host/src/gs/gs_gl.c tools/gs_gl_egl.c -o gs_replay_gl -ldl -lpthread -lm
* REPLAY_SOFT=1: the software GS in the same binary; REPLAY_GLNULL=1: GL calls do nothing (the
* CPU cost of the front end and the batching alone); REPLAY_VRAMSYNC=1: GPU targets read back into
* GS memory at each vsync, so the vram hash compares */
#define _GNU_SOURCE
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <ucontext.h>
#include <sys/time.h>
#include GS_C
/* REPLAY_PROF=file: a histogram of the program counter, 1 ms of CPU time apart ("eip tid count") */
static u32 prof_eip[1 << 16], prof_n[1 << 16];
static void on_prof(int sig, siginfo_t *si, void *ctxv)
{
u32 eip = (u32)((ucontext_t *)ctxv)->uc_mcontext.gregs[
#ifdef __x86_64__
REG_RIP
#else
REG_EIP
#endif
], h = (eip * 2654435761u) >> 16;
(void)sig; (void)si;
for (int k = 0; k < 64; k++) { u32 j = (h + k) & 0xFFFF; if (prof_n[j] && prof_eip[j] != eip) continue; prof_eip[j] = eip; prof_n[j]++; return; }
}
static void prof_dump(void)
{
FILE *f = fopen(getenv("REPLAY_PROF"), "w");
if (f) { for (int j = 0; j < 1 << 16; j++) if (prof_n[j]) fprintf(f, "%08x 0 %u\n", prof_eip[j], prof_n[j]); fclose(f); }
}
#ifdef GS_GL
#include "gs_gl_api.h"
int egl_gl_init(void);
int plat_gl_active;
static int gl_null;
int plat_gl_init(void) { return gl_null ? 1 : egl_gl_init(); }
void egl_gl_current(int on);
void plat_gl_current(int on) { if (!gl_null) egl_gl_current(on); }
void plat_gl_swap(void) {}
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
/* REPLAY_GLNULL: every GL call returns 0 (a few return what initialisation needs) */
static long gl_nop(void) { return 0; }
static void GLAPIENTRY gl_getiv(GLuint a, GLenum b, GLint *v) { (void)a; (void)b; *v = 1; }
static char gl_mapbuf[8 << 20];
static void *GLAPIENTRY gl_map(GLenum a, GLintptr b, GLsizeiptr c, GLbitfield d) { (void)a; (void)b; (void)c; (void)d; return gl_mapbuf; }
static GLuint gl_names;
static void GLAPIENTRY gl_gen(GLsizei n, GLuint *o) { for (int i = 0; i < n; i++) o[i] = ++gl_names; }
static GLuint GLAPIENTRY gl_create(void) { return ++gl_names; }
static GLuint GLAPIENTRY gl_create1(GLenum e) { (void)e; return ++gl_names; }
void *plat_gl_proc(const char *name);
static void *null_proc(const char *n)
{
if (!strcmp(n, "glGetShaderiv") || !strcmp(n, "glGetProgramiv")) return (void *)gl_getiv;
if (!strcmp(n, "glMapBufferRange")) return (void *)gl_map;
if (!strcmp(n, "glGenTextures") || !strcmp(n, "glGenBuffers") || !strcmp(n, "glGenVertexArrays") || !strcmp(n, "glGenFramebuffers")) return (void *)gl_gen;
if (!strcmp(n, "glCreateProgram")) return (void *)gl_create;
if (!strcmp(n, "glCreateShader")) return (void *)gl_create1;
return (void *)gl_nop;
}
#else
int plat_gl_active; /* (no GL in this build) */
int plat_gl_init(void) { return 0; }
void plat_gl_current(int on) { (void)on; }
void plat_gl_swap(void) {}
void plat_gl_size(int *w, int *h) { *w = 640; *h = 480; }
void *plat_gl_proc(const char *name) { (void)name; return 0; }
#endif
static uint32_t replay_vs;
uint32_t uya_frame_count(void) { return replay_vs; }
u32 vu_nonfinite;
const char *plat_exe_dir(void) { return "."; }
void uya_log(const char *fmt, ...)
{
if (getenv("REPLAY_QUIET")) return;
va_list ap; va_start(ap, fmt); fprintf(stderr, "[gs] "); vfprintf(stderr, fmt, ap); fputc('\n', stderr); va_end(ap);
}
int plat_present(const uint32_t *px, int w, int h) { (void)px; (void)w; (void)h; return 1; }
unsigned long long nsends, uya_t_vu;
void uya_vu_dump_kick(void) {}
void uya_vif1_dump_hist(void) {}
static uint32_t hash32(const void *p, size_t n) /* FNV-1a over words */
{
const uint32_t *w = p; uint32_t h = 2166136261u;
for (size_t i = 0; i < n / 4; i++) h = (h ^ w[i]) * 16777619u;
return h;
}
static double now_s(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec / 1e9; }
static double cpu_s(void) { struct timespec t; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t); return t.tv_sec + t.tv_nsec / 1e9; }
static void rd(FILE *f, void *p, size_t n) { if (fread(p, 1, n, f) != n) { fprintf(stderr, "short read\n"); exit(1); } }
int main(int argc, char **argv)
{
if (argc < 2) { fprintf(stderr, "usage: gs_replay rec.bin [frames_dir]\n"); return 1; }
if (!getenv("UYA_GS_THREADS")) setenv("UYA_GS_THREADS", "0", 1);
if (getenv("REPLAY_PROF")) {
struct sigaction sa; memset(&sa, 0, sizeof sa); sa.sa_flags = SA_SIGINFO | SA_RESTART; sa.sa_sigaction = on_prof; sigaction(SIGPROF, &sa, 0);
struct itimerval it = { { 0, 1000 }, { 0, 1000 } }; setitimer(ITIMER_PROF, &it, 0); atexit(prof_dump);
}
setenv("UYA_MTGS", "0", 1);
#ifdef GS_GL
if (!getenv("REPLAY_SOFT")) setenv("UYA_RENDERER", "gl", 1);
gl_null = getenv("REPLAY_GLNULL") != 0;
if (gl_null) { extern void *(*gl_proc_override)(const char *); gl_proc_override = null_proc; } /* the loader hands out the stubs */
#endif
FILE *f = fopen(argv[1], "rb");
if (!f) { perror(argv[1]); return 1; }
uya_gs_init();
u32 m; rd(f, &m, 4);
if (m != 0x52534755u) { fprintf(stderr, "not a GS recording\n"); return 1; }
#define X(fl) { u32 n_; rd(f, &n_, 4); if (n_ != sizeof fl) { fprintf(stderr, "field " #fl " size %u, expected %u\n", n_, (u32)sizeof fl); return 1; } rd(f, &fl, sizeof fl); }
GS_SNAP_FIELDS(X)
#undef X
rd(f, vram, 4 << 20);
static u32 buf[(0x10000 + 16) * 4];
static u32 *big; static size_t bign;
double t = 0, t0 = now_s(), c = 0;
u32 k;
while (fread(&k, 4, 1, f) == 1) {
if (k == 1) {
u32 h[2]; rd(f, h, 8);
u32 *d = buf;
if (h[1] > 0x10000) { if (bign < h[1] * 16) { free(big); big = malloc(h[1] * 16); bign = h[1] * 16; } d = big; }
rd(f, d, h[1] * 16);
double a = now_s(), ac = cpu_s();
uya_gs_src = path_name[h[0] < 3 ? h[0] : 2];
uya_gs_gif(d, h[1]);
t += now_s() - a; c += cpu_s() - ac;
} else if (k == 2) {
u64 r[6]; rd(f, r, 48);
double a = now_s(), ac = cpu_s();
u32 w = 0, hh = 0;
if (getenv("REPLAY_DISPFBP")) r[2] = (r[2] & ~0x1FFull) | ((u64)strtol(getenv("REPLAY_DISPFBP"), 0, 16) / 32); /* testing: show another buffer */
gs_drain();
int ok;
#ifdef GS_GL
if (gsgl_on) {
gs_disp_t dd;
ok = gs_disp(r[0], r[1], r[2], r[3], r[4], r[5], &dd);
if (ok && !gl_null) ok = gsgl_capture(&dd, frame_px);
else gsgl_flush();
w = dd.w; hh = dd.h;
if (getenv("REPLAY_VRAMSYNC")) gsgl_cpu_read(0, 0x4000);
if (ok && !gl_null && getenv("REPLAY_FULL") && argc > 2) { /* the upscaled picture too: full_NNNNN.ppm */
int gsgl_capture_full(const gs_disp_t *d, u32 *out, int *ow, int *oh);
static u32 *big; int fw, fh;
if (!big) big = malloc((size_t)4096 * 4096 * 4);
if (gsgl_capture_full(&dd, big, &fw, &fh)) {
char p[512]; snprintf(p, sizeof p, "%s/full_%05u.ppm", argv[2], replay_vs + 1);
FILE *o = fopen(p, "wb");
if (o) { fprintf(o, "P6 %d %d 255\n", fw, fh);
for (int y = 0; y < fh; y++) for (int x = 0; x < fw; x++) { u32 c = big[(size_t)y * fw + x]; fputc(c & 255, o); fputc((c >> 8) & 255, o); fputc((c >> 16) & 255, o); }
fclose(o); }
}
}
} else
#endif
ok = gs_capture(r[0], r[1], r[2], r[3], r[4], r[5], frame_px, &w, &hh);
t += now_s() - a; c += cpu_s() - ac;
replay_vs++;
printf("vsync %u vram %08X frame %08X\n", replay_vs, hash32(vram, 4 << 20), ok ? hash32(frame_px, (size_t)w * hh * 4) : 0);
if (argc > 2 && ok) {
char p[512]; snprintf(p, sizeof p, "%s/r_%05u.ppm", argv[2], replay_vs);
FILE *o = fopen(p, "wb");
if (o) { fprintf(o, "P6 %u %u 255\n", w, hh);
for (u32 i = 0; i < w * hh; i++) { u32 c = frame_px[i]; fputc((c >> 16) & 255, o); fputc((c >> 8) & 255, o); fputc(c & 255, o); }
fclose(o); }
}
} else if (k == 3) {
u32 h[4]; rd(f, h, 16);
u32 *px = malloc((size_t)h[2] * h[3] * 4); rd(f, px, (size_t)h[2] * h[3] * 4);
double a = now_s();
uya_gs_write_ct32(h[0], h[1], (int)h[2], (int)h[3], px);
t += now_s() - a;
free(px);
} else { fprintf(stderr, "bad record %u\n", k); return 1; }
}
#ifdef GS_GL
if (gsgl_on) gsgl_stats((int)replay_vs);
#endif
fprintf(stderr, "%u vsyncs: GS time %.3f s (%.2f ms/vsync), CPU %.2f ms/vsync, wall %.3f s\n", replay_vs, t, replay_vs ? 1000 * t / replay_vs : 0,
replay_vs ? 1000 * c / replay_vs : 0, now_s() - t0);
return 0;
}
+400
View File
@@ -0,0 +1,400 @@
#!/usr/bin/env python3
"""vu_refcheck.py DECOMP_REPO IMAGE VU [vucap_before.bin vucap_after.bin]
Testing aid for tools/vu_recomp.py: a plain interpreter of VU micro code that
replays a state the running wrapper captured (UYA_VUCAPTURE=image,n, see
host/src/gfx/vu.c) and compares its result with what the translated C
produced: registers, flags, Q and data memory. It works out stalls at run
time (the translator does it statically), so it also checks that model. The
MAC/status flags are only compared for images that read them (the
translation skips them otherwise). Needs numpy.
"""
import os, struct, sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from vu_isa import decode_pair, XYZW
f32 = np.float32
np.seterr(all="ignore")
def load_images():
d = open(os.path.join(sys.argv[1], "baserom", "SCES_509.16"), "rb").read()
e_shoff, = struct.unpack_from("<I", d, 0x20); shentsize, shnum, shstrndx = struct.unpack_from("<HHH", d, 0x2E)
secs = [struct.unpack_from("<10I", d, e_shoff + i * shentsize) for i in range(shnum)]
so = secs[shstrndx][4]
sizes = {}
for s in secs:
n = d[so + s[0]:d.index(b"\0", so + s[0])].decode()
if n.startswith(".DVP.overlay"): sizes[n] = s[5]
if n == ".DVP.ovlytab": tab = d[s[4]:s[4] + s[5]]
if n == ".DVP.ovlystrtab": st = d[s[4]:s[4] + s[5]]
imgs = {}
for i in range(0, len(tab) // 12 * 12, 12):
no, lma, vma = struct.unpack_from("<3I", tab, i)
n = st[no:st.index(b"\0", no)].decode()
g = n.split(".")[5]
imgs.setdefault(g, {})
b = d[lma - 0x100080 + 0x1000: lma - 0x100080 + 0x1000 + sizes[n]]
for k in range(0, len(b), 8):
imgs[g][vma + k] = struct.unpack_from("<II", b, k)
return imgs
FLAGREAD = {"fcand", "fcor", "fceq", "fcget", "fsand", "fsor", "fseq", "fmand", "fmor", "fmeq"}
def vi_written(lw):
n = lw.name
if n in ("iadd", "isub", "iand", "ior"): return lw.id_ & 15
if n in ("iaddi", "iaddiu", "isubiu", "ilw", "ilwr", "mtir", "xtop", "xitop", "bal", "jalr"): return lw.it & 15
if n in ("lqi", "lqd"): return lw.is_ & 15
if n in ("sqi", "sqd"): return lw.it & 15
return 0
SZ = 748 # sizeof(RcVu0), host/include/rc.h (keep in step with the struct)
def parse(path, vu):
b = open(path, "rb").read()
pc, = struct.unpack_from("<I", b, 0)
st = b[4:4 + SZ]
msz = 0x4000 if vu else 0x1000
mem = bytearray(b[4 + SZ:4 + SZ + msz])
top, itop = struct.unpack_from("<2I", b, 4 + SZ + msz) if len(b) >= 4 + SZ + msz + 8 else (0, 0)
vf = np.frombuffer(st[0:512], dtype=np.uint32).reshape(32, 4).copy()
acc = np.frombuffer(st[512:528], dtype=np.uint32).copy()
q, i, p, r = struct.unpack_from("<4I", st, 528)
vi = list(struct.unpack_from("<16I", st, 544))
clip, status, mac = struct.unpack_from("<3I", st, 608)
return dict(pc=pc, vf=vf, acc=acc, q=q, i=i, vi=vi, clip=clip, status=status, mac=mac, mem=mem, top=top, itop=itop)
def fl(u): return np.array([u], dtype=np.uint32).view(np.float32)[0]
def bits(x): return int(np.array([x], dtype=np.float32).view(np.uint32)[0])
class VU:
def __init__(s, st, code, vu):
s.vf = st["vf"]; s.acc = st["acc"]; s.vi = st["vi"]; s.q = st["q"]; s.i = st["i"]
s.clip = st["clip"]; s.status = st["status"]; s.mac = st["mac"]; s.mem = st["mem"]
s.code = code; s.vu = vu; s.qmask = 0x3FF if vu else 0xFF
s.cyc = 0; s.ready = {} # (reg, comp) -> cycle
s.fpipe = [] # (rdy, mac, status)
s.cpipe = []
s.cl_int = s.clip; s.st_int = s.status
s.qp = None # (rdy, val)
s.kicks = []
s.top, s.itop = st["top"], st["itop"]
s.vhist = []
s.targets = set()
for pc_, w_ in code.items():
l_ = decode_pair(*w_)[1]
if l_ is not None and l_.name in ("b", "bal", "ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
s.targets.add((pc_ + 8 + l_.imm * 8) & (0x3FF8 if vu else 0xFF8))
def F(s, r, c): return fl(int(s.vf[r][c]))
def setF(s, r, c, x):
if r: s.vf[r][c] = bits(x)
def memw(s, qa, c): o = ((qa & s.qmask) << 4) + 4 * c; return struct.unpack_from("<I", s.mem, o)[0]
def setmem(s, qa, c, v): o = ((qa & s.qmask) << 4) + 4 * c; struct.pack_into("<I", s.mem, o, v & 0xFFFFFFFF)
def flsync(s):
while s.fpipe and s.fpipe[0][0] <= s.cyc:
_, s.mac, s.status = s.fpipe.pop(0)
while s.cpipe and s.cpipe[0][0] <= s.cyc:
_, s.clip = s.cpipe.pop(0)
if s.qp and s.qp[0] <= s.cyc:
s.q = s.qp[1]; s.qp = None
def setvi(s, r, v):
if r & 15: s.vi[r & 15] = v & 0xFFFF
def run(s, pc, maxsteps=2000000):
bpending = None; ending = 0
steps = 0
while True:
steps += 1
if steps > maxsteps: raise Exception("too many steps")
lo, hi = s.code[pc]
u, l, flg, imm = decode_pair(lo, hi)
# stalls
reads = s.reads(u, l)
t = s.cyc + 1
need = max([s.ready.get(x, 0) for x in reads] + [0])
t = max(t, need)
if l is not None and l.name in ("waitq", "div", "sqrt", "rsqrt") and s.qp:
t = max(t, s.qp[0])
stalled = t > s.cyc + 1
s.cyc = t
s.flsync()
# upper
ures = s.upper(u)
# lower
nb = None
before_vi = list(s.vi)
if imm is not None:
s.i = imm
elif l is not None:
# branch VI delay (see tools/vu_recomp.py): the branch reads the value
# from before a run of writes to its register right before it
patch = {}
if l.name in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez", "jr", "jalr") and not stalled and pc not in s.targets:
regs = [l.is_ & 15] + ([l.it & 15] if l.name in ("ibeq", "ibne") else [])
for x in regs:
if not x: continue
h = s.vhist
if not h or h[-1][0] != x or h[-1][2]: continue
k = len(h) - 1
while k > 0 and len(h) - k < 4 and h[k - 1][0] == x and not h[k - 1][2] and not h[k][3] and not h[k][4]:
k -= 1
patch[x] = h[k][1]
break
real = {x: s.vi[x] for x in patch}
for x, val in patch.items(): s.vi[x] = val
nb = s.lower(l, pc)
for x, val in patch.items():
if s.vi[x] == val: s.vi[x] = real[x]
# history of VI writes: (reg, value before, reads flags, stalled, branch target)
if l is not None and imm is None:
wr = vi_written(l)
s.vhist.append((wr, before_vi[wr] if wr else 0, l.name in FLAGREAD, stalled, pc in s.targets))
if len(s.vhist) > 8: s.vhist.pop(0)
else:
s.vhist.append((0, 0, False, stalled, pc in s.targets))
if len(s.vhist) > 8: s.vhist.pop(0)
# commit upper
if ures: ures()
for x in s.writes(u, l):
s.ready[x] = s.cyc + 4
# control
if ending:
return pc + 8
if bpending is not None:
tgt = bpending; bpending = None
if flg["E"]: ending = 1
pc = tgt
continue
if flg["E"]:
ending = 1
if nb is not None:
bpending = nb
pc = pc + 8
def reads(s, u, l):
r = []
k = u.kind
if k == "arith":
for c in u.comps:
r.append((u.fs, c))
if u.bc in ("x","y","z","w"): r.append((u.ft, XYZW.index(u.bc)))
elif u.bc is None: r.append((u.ft, c))
elif k in ("opmula", "opmsub"):
for c in range(3): r += [(u.fs, c), (u.ft, c)]
elif k in ("itof", "ftoi", "abs"): r += [(u.fs, c) for c in u.comps]
elif k == "clip": r += [(u.fs, 0), (u.fs, 1), (u.fs, 2), (u.ft, 3)]
if l is not None:
n = l.name
if n in ("sq", "sqi", "sqd", "move"): r += [(l.fs, c) for c in l.comps]
elif n == "mr32": r += [(l.fs, (c + 1) & 3) for c in l.comps]
elif n == "mtir": r.append((l.fs, l.fsf))
elif n == "div" or n == "rsqrt": r += [(l.fs, l.fsf), (l.ft, l.ftf)]
elif n == "sqrt": r.append((l.ft, l.ftf))
return [x for x in r if x[0]]
def writes(s, u, l):
w = []
k = u.kind
if k == "arith" and not u.acc: w += [(u.fd, c) for c in u.comps]
elif k == "opmsub": w += [(u.fd, c) for c in u.comps if c < 3]
elif k in ("itof", "ftoi", "abs"): w += [(u.ft, c) for c in u.comps]
if l is not None and l.name in ("lq", "lqi", "lqd", "move", "mr32", "mfir"):
w += [(l.ft, c) for c in l.comps]
return [x for x in w if x[0]]
def upper(s, u):
k = u.kind
if k == "nop": return None
if k == "arith":
base = u.base if u.acc else u.name
def B(c):
if u.bc in ("x","y","z","w"): return s.F(u.ft, XYZW.index(u.bc))
if u.bc == "q": return fl(s.q)
if u.bc == "i": return fl(s.i)
return s.F(u.ft, c)
res = {}
for c in u.comps:
a, b = s.F(u.fs, c), B(c)
accv = fl(int(s.acc[c]))
if base == "add": x = f32(a + b)
elif base == "sub": x = f32(a - b)
elif base == "mul": x = f32(a * b)
elif base == "madd": x = f32(accv + f32(a * b))
elif base == "msub": x = f32(accv - f32(a * b))
elif base == "max": x = np.fmax(a, b)
elif base == "mini": x = np.fmin(a, b)
res[c] = x
def commit():
for c, x in res.items():
if u.acc: s.acc[c] = bits(x)
elif u.fd: s.vf[u.fd][c] = bits(x)
if base in ("add", "sub", "mul", "madd", "msub"):
s.push_flags(res)
return commit
if k in ("opmula", "opmsub"):
ex = {0: (1, 2), 1: (2, 0), 2: (0, 1)}
res = {}
for c in [c for c in u.comps if c < 3]:
a, b = ex[c]
pr = f32(s.F(u.fs, a) * s.F(u.ft, b))
res[c] = pr if k == "opmula" else f32(fl(int(s.acc[c])) - pr)
def commit():
for c, x in res.items():
if k == "opmula": s.acc[c] = bits(x)
elif u.fd: s.vf[u.fd][c] = bits(x)
s.push_flags(res)
return commit
if k == "itof":
res = {c: bits(f32(np.int32(np.uint32(s.vf[u.fs][c]).view(np.int32)) / f32(1 << u.shift))) for c in u.comps}
def commit():
for c, x in res.items():
if u.ft: s.vf[u.ft][c] = x
return commit
if k == "ftoi":
res = {}
for c in u.comps:
x = float(s.F(u.fs, c)) * (1 << u.shift)
x = f32(x)
if x != x: v = 0
elif x >= 2147483647.0: v = 0x7FFFFFFF
elif x <= -2147483648.0: v = -0x80000000
else: v = int(x)
res[c] = v & 0xFFFFFFFF
def commit():
for c, x in res.items():
if u.ft: s.vf[u.ft][c] = x
return commit
if k == "abs":
res = {c: int(s.vf[u.fs][c]) & 0x7FFFFFFF for c in u.comps}
def commit():
for c, x in res.items():
if u.ft: s.vf[u.ft][c] = x
return commit
if k == "clip":
w = abs(s.F(u.ft, 3)); x, y, z = s.F(u.fs, 0), s.F(u.fs, 1), s.F(u.fs, 2)
f = (x > w) | ((x < -w) << 1) | ((y > w) << 2) | ((y < -w) << 3) | ((z > w) << 4) | ((z < -w) << 5)
def commit():
s.cl_int = ((s.cl_int << 6) | int(f)) & 0xFFFFFF
s.cpipe.append((s.cyc + 4, s.cl_int))
return commit
raise Exception("upper " + u.name)
def push_flags(s, res):
mac = 0
for c, x in res.items():
b = bits(x)
if (b & 0x7FFFFFFF) == 0: mac |= 8 >> c
if b >> 31: mac |= 0x80 >> c
st = (s.st_int & 0xFC0) | (1 if mac & 0xF else 0) | (2 if mac & 0xF0 else 0)
st |= (st & 3) << 6
s.st_int = st
s.fpipe.append((s.cyc + 4, mac, st))
def lower(s, l, pc):
n = l.name; vi = s.vi
V = lambda r: vi[r & 15] if r & 15 else 0
if n == "move":
vals = [int(s.vf[l.fs][c]) for c in range(4)]
for c in l.comps:
if l.ft: s.vf[l.ft][c] = vals[c]
elif n == "mr32":
vals = [int(s.vf[l.fs][(c + 1) & 3]) for c in range(4)]
for c in l.comps:
if l.ft: s.vf[l.ft][c] = vals[c]
elif n in ("lq", "lqi", "lqd"):
if n == "lqd": s.setvi(l.is_, V(l.is_) - 1)
a = V(l.is_) + (l.imm if n == "lq" else 0)
for c in l.comps:
if l.ft: s.vf[l.ft][c] = s.memw(a, c)
if n == "lqi": s.setvi(l.is_, V(l.is_) + 1)
elif n in ("sq", "sqi", "sqd"):
if n == "sqd": s.setvi(l.it, V(l.it) - 1)
a = V(l.it) + (l.imm if n == "sq" else 0)
for c in l.comps: s.setmem(a, c, int(s.vf[l.fs][c]))
if n == "sqi": s.setvi(l.it, V(l.it) + 1)
elif n in ("ilw", "ilwr"):
a = V(l.is_) + (l.imm if n == "ilw" else 0)
s.setvi(l.it, s.memw(a, l.comps[0]))
elif n in ("isw", "iswr"):
a = V(l.is_) + (l.imm if n == "isw" else 0)
for c in l.comps: s.setmem(a, c, V(l.it))
elif n == "iadd": s.setvi(l.id_, V(l.is_) + V(l.it))
elif n == "isub": s.setvi(l.id_, V(l.is_) - V(l.it))
elif n == "iand": s.setvi(l.id_, V(l.is_) & V(l.it))
elif n == "ior": s.setvi(l.id_, V(l.is_) | V(l.it))
elif n == "iaddi": s.setvi(l.it, V(l.is_) + l.imm)
elif n == "iaddiu": s.setvi(l.it, V(l.is_) + l.imm)
elif n == "isubiu": s.setvi(l.it, V(l.is_) - l.imm)
elif n == "mtir": s.setvi(l.it, int(s.vf[l.fs][l.fsf]))
elif n == "mfir":
v = V(l.is_); v = v - 0x10000 if v & 0x8000 else v
for c in l.comps:
if l.ft: s.vf[l.ft][c] = v & 0xFFFFFFFF
elif n in ("div", "sqrt", "rsqrt"):
if s.qp: s.q = s.qp[1]; s.qp = None
if n == "div":
a, b = s.F(l.fs, l.fsf), s.F(l.ft, l.ftf)
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if (bits(a) ^ bits(b)) & 0x80000000 else 0x7F7FFFFF)
s.qp = (s.cyc + 7, v)
elif n == "sqrt":
s.qp = (s.cyc + 7, bits(f32(np.sqrt(abs(s.F(l.ft, l.ftf))))))
else:
a, b = s.F(l.fs, l.fsf), f32(np.sqrt(abs(s.F(l.ft, l.ftf))))
v = bits(f32(a / b)) if b != 0 else (0xFF7FFFFF if bits(a) & 0x80000000 else 0x7F7FFFFF)
s.qp = (s.cyc + 13, v)
elif n == "waitq": pass
elif n == "fcand": s.setvi(1, 1 if (s.clip & l.imm) else 0)
elif n == "fcor": s.setvi(1, 1 if ((s.clip | l.imm) & 0xFFFFFF) == 0xFFFFFF else 0)
elif n == "fceq": s.setvi(1, 1 if (s.clip & 0xFFFFFF) == l.imm else 0)
elif n == "fcset":
s.cpipe = []; s.clip = s.cl_int = l.imm
elif n == "fcget": s.setvi(l.it, s.clip & 0xFFF)
elif n == "fsand": s.setvi(l.it, s.status & l.imm)
elif n == "fmand": s.setvi(l.it, V(l.is_) & s.mac)
elif n == "fmor": s.setvi(l.it, V(l.is_) | s.mac)
elif n == "xgkick": s.kicks.append(V(l.is_) & 0x3FF)
elif n == "xtop": s.setvi(l.it, s.top)
elif n == "xitop": s.setvi(l.it, s.itop)
elif n == "b": return (pc + 8 + l.imm * 8) & (0x3FF8 if s.vu else 0xFF8)
elif n == "bal": s.setvi(l.it, (pc + 16) // 8); return (pc + 8 + l.imm * 8)
elif n == "jr": return (V(l.is_) * 8)
elif n == "jalr":
t = V(l.is_) * 8; s.setvi(l.it, (pc + 16) // 8); return t
elif n in ("ibeq", "ibne", "ibltz", "ibgtz", "iblez", "ibgez"):
a, b = V(l.it), V(l.is_)
sb = b - 0x10000 if b & 0x8000 else b
c = {"ibeq": a == b, "ibne": a != b, "ibltz": sb < 0, "ibgtz": sb > 0, "iblez": sb <= 0, "ibgez": sb >= 0}[n]
if c: return pc + 8 + l.imm * 8
else:
raise Exception("lower " + n)
return None
def main():
g, vu = sys.argv[2], int(sys.argv[3])
bp = sys.argv[4] if len(sys.argv) > 4 else "vucap_before.bin"
ap = sys.argv[5] if len(sys.argv) > 5 else "vucap_after.bin"
code = load_images()[g]
reads_flags = any(decode_pair(*w)[1] is not None and decode_pair(*w)[1].name in
("fsand", "fsor", "fseq", "fmand", "fmor", "fmeq") for w in code.values())
st = parse(bp, vu); af = parse(ap, vu)
m = VU(st, code, vu)
m.run(st["pc"])
while m.fpipe: _, m.mac, m.status = m.fpipe.pop(0)
while m.cpipe: _, m.clip = m.cpipe.pop(0)
if m.qp: m.q = m.qp[1]
diffs = 0
for r in range(32):
for c in range(4):
if int(m.vf[r][c]) != int(af["vf"][r][c]):
diffs += 1
if diffs < 30: print("vf%d.%s ref %08X C %08X" % (r, XYZW[c], int(m.vf[r][c]), int(af["vf"][r][c])))
for r in range(16):
if m.vi[r] != af["vi"][r]: diffs += 1; print("vi%d ref %04X C %04X" % (r, m.vi[r], af["vi"][r]))
for c in range(4):
if int(m.acc[c]) != int(af["acc"][c]): diffs += 1; print("acc.%s ref %08X C %08X" % (XYZW[c], int(m.acc[c]), int(af["acc"][c])))
for nm in ("q", "clip") + (("status", "mac") if reads_flags else ()):
if getattr(m, nm) != af[nm]: diffs += 1; print("%s ref %08X C %08X" % (nm, getattr(m, nm), af[nm]))
md = [k for k in range(0, len(m.mem), 4) if m.mem[k:k+4] != af["mem"][k:k+4]]
if md:
diffs += len(md)
print("memory differs at %d words, first at qw 0x%X" % (len(md), md[0] // 16))
for k in md[:10]:
print(" qw %03X.%s ref %08X C %08X" % (k // 16, XYZW[(k // 4) & 3], struct.unpack_from("<I", m.mem, k)[0], struct.unpack_from("<I", af["mem"], k)[0]))
print("kicks:", ["%03X" % k for k in m.kicks][:20], "cycles", m.cyc)
print("DIFFS", diffs)
if __name__ == "__main__":
main()