/* emu860c.c -- M4a: the i860 step-core in C (CPython extension). * * A mechanical port of emu860.py's validated semantics (the golden model): * paged memory, integer ALU/loads/stores, control flow with delayed branches, * bc.t/bnc.t annul, dual-instruction-mode tracking, and the FP pipeline model. * * Differential contract: emu860c must reproduce emu860.py's (step, pc, * reg-crc) checkpoints exactly (scratchpad ref_trace / emu860_ref_trace.pkl). * * Stage 1 (this file, first landing): memory + integer core + control flow + * DIM/annul + scalar FP moves/loads/stores + the pipe mechanics for the * common pipelined ops (pfadd/pfsub/pfmul + fnop). Anything else FP raises * NotImplemented back to Python (run_until returns reason=2 with the pc), so * divergence is impossible -- unfinished territory just falls back. */ #define PY_SSIZE_T_CLEAN #include #include #include #include #define MASK32 0xffffffffu /* ---------------- paged guest memory ---------------- */ #define PAGE_BITS 12 #define PAGE_SIZE (1u << PAGE_BITS) #define N_PAGES (1u << (32 - PAGE_BITS)) static uint8_t **g_pages; /* N_PAGES pointers, allocated on demand */ static uint8_t *page_for(uint32_t addr, int create) { uint32_t pi = addr >> PAGE_BITS; uint8_t *p = g_pages[pi]; if (!p && create) { p = (uint8_t *)calloc(PAGE_SIZE, 1); g_pages[pi] = p; } return p; } static uint32_t mem_r32(uint32_t addr) { uint32_t off = addr & (PAGE_SIZE - 1); uint8_t *p = page_for(addr, 0); if (!p) return 0; if (off <= PAGE_SIZE - 4) { uint32_t v; memcpy(&v, p + off, 4); return v; } /* page-spanning byte fallback */ uint32_t v = 0; for (int i = 0; i < 4; i++) { uint8_t *q = page_for(addr + i, 0); v |= (uint32_t)(q ? q[(addr + i) & (PAGE_SIZE - 1)] : 0) << (8 * i); } return v; } /* ---- write-watch (M4b): ranges + append log ---- */ static uint32_t g_watch[8][2]; static int g_nwatch; static uint32_t *g_wbuf; static size_t g_wlen, g_wcap; static inline void watch_note(uint32_t addr, uint32_t val) { for (int i = 0; i < g_nwatch; i++) { if (addr >= g_watch[i][0] && addr < g_watch[i][1]) { if (g_wlen + 2 > g_wcap) { g_wcap = g_wcap ? g_wcap * 2 : 65536; g_wbuf = (uint32_t *)realloc(g_wbuf, g_wcap * 4); } g_wbuf[g_wlen++] = addr; g_wbuf[g_wlen++] = val; return; } } } static void mem_w32(uint32_t addr, uint32_t val) { uint32_t off = addr & (PAGE_SIZE - 1); if (g_nwatch) watch_note(addr, val); uint8_t *p = page_for(addr, 1); if (off <= PAGE_SIZE - 4) { memcpy(p + off, &val, 4); return; } for (int i = 0; i < 4; i++) { uint8_t *q = page_for(addr + i, 1); q[(addr + i) & (PAGE_SIZE - 1)] = (uint8_t)(val >> (8 * i)); } } static uint32_t mem_r16(uint32_t addr) { uint8_t *p = page_for(addr, 0); uint32_t off = addr & (PAGE_SIZE - 1); if (p && off <= PAGE_SIZE - 2) { uint16_t v; memcpy(&v, p + off, 2); return v; } uint32_t v = 0; for (int i = 0; i < 2; i++) { uint8_t *q = page_for(addr + i, 0); v |= (uint32_t)(q ? q[(addr + i) & (PAGE_SIZE - 1)] : 0) << (8 * i); } return v; } static void mem_w16(uint32_t addr, uint32_t val) { for (int i = 0; i < 2; i++) { uint8_t *q = page_for(addr + i, 1); q[(addr + i) & (PAGE_SIZE - 1)] = (uint8_t)(val >> (8 * i)); } } static uint32_t mem_r8(uint32_t addr) { uint8_t *p = page_for(addr, 0); return p ? p[addr & (PAGE_SIZE - 1)] : 0; } static void mem_w8(uint32_t addr, uint32_t val) { uint8_t *p = page_for(addr, 1); p[addr & (PAGE_SIZE - 1)] = (uint8_t)val; } /* ---------------- CPU state ---------------- */ typedef struct { double v; int rp; int has_raw; uint64_t raw; } PipeEnt; typedef struct { uint32_t r[32]; uint32_t f[32]; uint32_t cr[16]; uint32_t pc; int lcc; /* delayed branch */ int br_pending; uint32_t br_target; int br_delayed; int squash; /* DIM */ int dim, dim_on, dim_exit, dim_half; /* FP pipes */ PipeEnt apipe[3], mpipe[3]; uint32_t lpipe_lo[3]; uint32_t lpipe_hi[3]; int lpipe_has_hi[3]; uint32_t gpipe; double kr, ki, t; /* control */ int stop; char stopmsg[256]; uint64_t steps; uint32_t ret_sentinel; } Cpu; static Cpu C; static uint32_t rd(int i) { return i == 0 ? 0 : C.r[i]; } static void wr(int i, uint32_t v) { if (i) C.r[i] = v; } static uint32_t frd(int i) { return i <= 1 ? 0 : C.f[i]; } static void fwr(int i, uint32_t v) { if (i > 1) C.f[i] = v; } static int cc(void) { return (C.cr[1] >> 2) & 1; } static void set_cc(int cond) { if (cond) C.cr[1] |= 4; else C.cr[1] &= ~4u; } static int32_t s16(uint32_t v) { return (int32_t)(int16_t)(v & 0xffff); } static int32_t s26(uint32_t v) { return (v & 0x02000000) ? (int32_t)(v | 0xfc000000u) : (int32_t)v; } static float b2f(uint32_t b) { float f; memcpy(&f, &b, 4); return f; } static uint32_t f2b(float f) { uint32_t b; memcpy(&b, &f, 4); return b; } static double rdf(int reg, int dbl) { if (dbl) { int b = reg & ~1; uint64_t bits = ((uint64_t)frd(b | 1) << 32) | frd(b); double d; memcpy(&d, &bits, 8); return d; } return (double)b2f(frd(reg)); } static void wrf(int reg, double val, int dbl) { if (dbl) { uint64_t bits; memcpy(&bits, &val, 8); int b = reg & ~1; fwr(b, (uint32_t)bits); fwr(b | 1, (uint32_t)(bits >> 32)); } else { fwr(reg, f2b((float)val)); } } static double round_rp(double v, int rp) { return rp ? v : (double)b2f(f2b((float)v)); } static PipeEnt padv(PipeEnt *pipe, double val, int rp, int depth, int has_raw, uint64_t raw) { PipeEnt out = pipe[depth - 1]; for (int i = depth - 1; i > 0; i--) pipe[i] = pipe[i - 1]; pipe[0].v = has_raw ? val : round_rp(val, rp); pipe[0].rp = rp; pipe[0].has_raw = has_raw; pipe[0].raw = raw; return out; } static void retire(int dest, PipeEnt e) { if (!e.has_raw) { wrf(dest, e.v, e.rp); } else if (e.rp) { int b0 = dest & ~1; fwr(b0, (uint32_t)e.raw); fwr(b0 | 1, (uint32_t)(e.raw >> 32)); } else { fwr(dest, (uint32_t)e.raw); } } static void branch(uint32_t target, int delayed) { C.br_pending = 1; C.br_target = target; C.br_delayed = delayed; } /* returns 0 ok, 1 = stop, 2 = FP-not-implemented (fallback to Python) */ static int exec_fp(uint32_t w, int src1, int src2, int dest); /* ---------------- the DIM tracker (port of _dim_track) ---------------- */ static void dim_track(uint32_t w, uint32_t pc) { (void)pc; int fp = (((w >> 26) & 0x3f) == 0x12) || ((w & 0xfffffdffu) == 0xb0000000u); int d_bit = (w & 0x200) != 0; if (!C.dim) { if (fp && d_bit && !C.dim_on) { C.dim_on = 1; return; } if (C.dim_on) { C.dim = 1; C.dim_on = 0; C.dim_half = 0; return; } } else { /* in DIM: halves alternate positionally */ if (C.dim_half == 0) { /* FP half */ if (fp && !d_bit && !C.dim_exit) C.dim_exit = 1; else if (C.dim_exit == 1 && fp && !d_bit) C.dim_exit = 1; C.dim_half = 1; } else { C.dim_half = 0; if (C.dim_exit) { C.dim_exit++; if (C.dim_exit >= 3) { C.dim = 0; C.dim_exit = 0; } } } } } /* ---------------- execute one instruction word ---------------- */ static int execute(uint32_t w, uint32_t pc) { int op = (w >> 26) & 0x3f; int dest = (w >> 16) & 0x1f; int src2 = (w >> 21) & 0x1f; int src1 = (w >> 11) & 0x1f; uint32_t imm = w & 0xffff; switch (op) { /* ---- integer loads ---- */ case 0x00: case 0x01: { /* ld.b */ uint32_t ea = (op & 1) ? rd(src2) + s16(imm) : rd(src2) + rd(src1); uint32_t b = mem_r8(ea); wr(dest, (uint32_t)(int32_t)(int8_t)b); return 0; } case 0x04: case 0x05: { /* ld.s / ld.l via bit0 */ uint32_t off = (op & 1) ? (uint32_t)s16(imm & 0xfffe ? imm : imm) : rd(src1); int isl = imm & 1; if (op & 1) { if (isl) off = (uint32_t)s16(imm & 0xfffc); else off = (uint32_t)s16(imm & 0xfffe); } else { isl = imm & 1; /* register-indexed: bit0 of imm still selects */ } uint32_t ea = rd(src2) + off; if (isl) wr(dest, mem_r32(ea)); else wr(dest, (uint32_t)(int32_t)(int16_t)mem_r16(ea)); return 0; } /* ---- integer stores (split offset) ---- */ case 0x03: { /* st.b */ uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff); mem_w8(rd(src2) + s16(off), rd(src1) & 0xff); return 0; } case 0x07: { /* st.s / st.l */ uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff); if (off & 1) mem_w32(rd(src2) + s16(off & 0xfffc), rd(src1)); else mem_w16(rd(src2) + s16(off & 0xfffe), rd(src1) & 0xffff); return 0; } /* ---- FP loads/stores ---- */ case 0x08: case 0x09: case 0x0a: case 0x0b: { int fl = w & 7; int size = (fl & 2) ? 4 : ((fl & 4) ? 16 : 8); uint32_t off; if (op & 1) off = (uint32_t)s16(imm & (0x10000 - size)); else off = rd(src1); uint32_t ea = rd(src2) + off; if (fl & 1) wr(src2, ea); int b0 = dest & ~((size / 4) - 1); if (op < 0x0a) { for (int i = 0; i < size / 4; i++) fwr(b0 + i, mem_r32(ea + 4 * i)); } else { for (int i = 0; i < size / 4; i++) mem_w32(ea + 4 * i, frd(b0 + i)); } return 0; } case 0x18: case 0x19: { /* pfld */ int fl = w & 7; int size = (fl & 2) ? 4 : 8; uint32_t off; if (op & 1) off = (uint32_t)s16(imm & (0x10000 - size)); else off = rd(src1); uint32_t ea = rd(src2) + off; if (fl & 1) wr(src2, ea); uint32_t lo = C.lpipe_lo[2], hi = C.lpipe_hi[2]; int has_hi = C.lpipe_has_hi[2]; C.lpipe_lo[2] = C.lpipe_lo[1]; C.lpipe_hi[2] = C.lpipe_hi[1]; C.lpipe_has_hi[2] = C.lpipe_has_hi[1]; C.lpipe_lo[1] = C.lpipe_lo[0]; C.lpipe_hi[1] = C.lpipe_hi[0]; C.lpipe_has_hi[1] = C.lpipe_has_hi[0]; C.lpipe_lo[0] = mem_r32(ea); C.lpipe_has_hi[0] = (size == 8); C.lpipe_hi[0] = (size == 8) ? mem_r32(ea + 4) : 0; if (!has_hi) { fwr(dest, lo); } else { int b0 = dest & ~1; fwr(b0, lo); fwr(b0 | 1, hi); } return 0; } case 0x0d: { /* flush */ uint32_t off = ((w >> 16) & 0x1f) << 11 | (w & 0x7ff); if (off & 1) wr(src2, rd(src2) + s16(off & 0xffe0)); return 0; } case 0x0c: wr(dest, C.cr[src2 & 15]); return 0; /* ld.c */ case 0x0e: C.cr[src2 & 15] = rd(src1); return 0; /* st.c */ case 0x02: fwr(dest, rd(src1)); return 0; /* ixfr */ /* ---- control transfer ---- */ case 0x10: branch(rd(src1), 1); return 0; /* bri */ case 0x13: { /* core escape */ int sub = w & 0x1f; if (sub == 0x02) { wr(1, pc + 8); branch(rd(src1), 1); return 0; } if (sub == 0x01 || sub == 0x07 || sub == 0x04) return 0; C.stop = 1; snprintf(C.stopmsg, sizeof C.stopmsg, "core-escape sub %#x @ %#x", sub, pc); return 1; } case 0x1a: branch(pc + 4 + s26(w & 0x03ffffff) * 4, 1); return 0; /* br */ case 0x1b: wr(1, pc + 8); branch(pc + 4 + s26(w & 0x03ffffff) * 4, 1); return 0; /* call */ case 0x1c: case 0x1d: { /* bc / bc.t */ uint32_t tgt = pc + 4 + s26(w & 0x03ffffff) * 4; if (cc()) branch(tgt, op == 0x1d); else if (op == 0x1d) C.squash = 1; return 0; } case 0x1e: case 0x1f: { /* bnc / bnc.t */ uint32_t tgt = pc + 4 + s26(w & 0x03ffffff) * 4; if (!cc()) branch(tgt, op == 0x1f); else if (op == 0x1f) C.squash = 1; return 0; } case 0x14: case 0x15: case 0x16: case 0x17: { /* btne/bte */ int32_t broff = s16(((dest << 11) | (w & 0x7ff)) & 0xffff); uint32_t a = (op & 1) ? (uint32_t)src1 : rd(src1); uint32_t b = rd(src2); int take = (op < 0x16) ? (a != b) : (a == b); if (take) branch(pc + 4 + broff * 4, 0); return 0; } case 0x2d: { /* bla */ uint32_t a = rd(src1), b = rd(src2); int taken = C.lcc; if ((int32_t)a < 0) C.lcc = ((int32_t)(a + b) >= 0) ? 1 : 0; else C.lcc = ((uint64_t)a + (uint64_t)b > MASK32) ? 1 : 0; wr(src2, a + b); if (taken) { int32_t off = s16(((((w >> 16) & 0x1f) << 11) | (w & 0x7ff)) & 0xffff); branch(pc + 4 + off * 4, 1); } return 0; } default: break; } /* ---- arithmetic / logic ---- */ { int base = op & 0x3e; if (base >= 0x20 && base <= 0x26) { /* addu subu adds subs */ uint32_t b = rd(src2); uint32_t a = (op & 1) ? (uint32_t)s16(imm) : rd(src1); if (base == 0x20) { uint64_t s = (uint64_t)a + b; wr(dest, (uint32_t)s); set_cc(s > MASK32); } else if (base == 0x24) { int64_t s = (int64_t)(int32_t)a + (int32_t)b; wr(dest, (uint32_t)s); set_cc(((uint32_t)s & 0x80000000u) != 0); } else if (base == 0x22) { wr(dest, a - b); set_cc(a < b); } else { wr(dest, a - b); set_cc((int32_t)a < (int32_t)b); } return 0; } if (base >= 0x28 && base <= 0x2e) { /* shifts (incl shrd) */ int cnt = (op & 1) ? (s16(imm) & 0x1f) : (rd(src1) & 0x1f); uint32_t b = rd(src2); uint32_t r; if (base == 0x28) r = b << cnt; else if (base == 0x2a) r = b >> cnt; else if (base == 0x2e) r = (uint32_t)((int32_t)b >> cnt); else r = b >> cnt; wr(dest, r); return 0; } if (base >= 0x30) { /* and..xorh */ uint32_t a = (op & 1) ? imm : rd(src1); int hi = (base == 0x32 || base == 0x36 || base == 0x3a || base == 0x3e); if (hi && (op & 1)) a = imm << 16; uint32_t b = rd(src2); int g = base & 0x3c; uint32_t r; if (g == 0x30) r = b & a; else if (g == 0x34) r = b & ~a; else if (g == 0x38) r = b | a; else r = b ^ a; wr(dest, r); set_cc(r == 0); return 0; } } if (op == 0x12) return exec_fp(w, src1, src2, dest); C.stop = 1; snprintf(C.stopmsg, sizeof C.stopmsg, "unimplemented op %#x @ %#x w=%#x", op, pc, w); return 1; } /* ---------------- FP unit: full port of emu860.exec_fp ---------------- */ /* dual-op routing table: {m1, m2, a1, a2, kload, tload}; operand tags: * 0=s1 1=s2 2=kr 3=ki 4=t 5=PP(flagm) 6=mm */ static const int DUAL[16][6] = { {2,1, 0,6, 0,0}, {2,1, 4,6, 1,0}, {2,1, 0,5, 0,1}, {2,1, 4,5, 1,1}, {3,1, 0,6, 0,0}, {3,1, 4,6, 1,0}, {3,1, 0,5, 0,1}, {3,1, 4,5, 1,1}, {2,5, 0,1, 0,1}, {0,1, 5,6, 0,0}, {2,5, 0,1, 0,0}, {0,1, 4,5, 0,1}, {3,5, 0,1, 0,1}, {0,1, 4,6, 0,0}, {3,5, 0,1, 0,0}, {0,1, 4,5, 0,0}, }; static int exec_fp(uint32_t w, int src1, int src2, int dest) { if ((w & 0xfffffdffu) == 0xb0000000u) return 0; /* fnop / d.fnop */ int sub = w & 0x7f; int sp = (w & 0x100) ? 1 : 0; int rp = (w & 0x080) ? 1 : 0; int pbit = (w >> 10) & 1; if (sub < 0x20) { /* dual ops PF[M]AM / PF[M]SM */ int mdepth = sp ? 2 : 3; PipeEnt am = C.apipe[2]; PipeEnt mm = C.mpipe[mdepth - 1]; double am_out = am.v, mm_out = mm.v; double pipe_val = pbit ? am_out : mm_out; const int *dd = DUAL[sub & 0xf]; double vals_sp[7], vals_rp[7]; vals_sp[0] = rdf(src1, sp); vals_sp[1] = rdf(src2, sp); vals_rp[0] = rdf(src1, rp); vals_rp[1] = rdf(src2, rp); vals_sp[2] = vals_rp[2] = C.kr; vals_sp[3] = vals_rp[3] = C.ki; vals_sp[4] = vals_rp[4] = C.t; vals_sp[5] = vals_rp[5] = pipe_val; vals_sp[6] = vals_rp[6] = mm_out; double v1 = vals_sp[dd[0]], v2 = vals_sp[dd[1]]; if (dd[1] == 1 && dest && src2 == dest) v2 = pipe_val; double newm = v1 * v2; double u1 = vals_rp[dd[2]], u2 = vals_rp[dd[3]]; if (dd[2] == 0 && dest && src1 == dest) u1 = pipe_val; if (dd[3] == 1 && dest && src2 == dest) u2 = pipe_val; double newa = (sub & 0x10) ? (u1 - u2) : (u1 + u2); if (dd[5]) C.t = mm_out; if (dd[4]) { if (dd[0] == 3) C.ki = rdf(src1, sp); else C.kr = rdf(src1, sp); } if (pbit) retire(dest, C.apipe[2]); else retire(dest, C.mpipe[mdepth - 1]); padv(C.apipe, newa, rp, 3, 0, 0); padv(C.mpipe, newm, rp, mdepth, 0, 0); return 0; } if (sub == 0x20 || sub == 0x24) { /* fmul / pfmul (0x24 = 3-stage) */ double v1 = rdf(src1, sp), v2 = rdf(src2, sp); if (pbit) { int depth = (sub == 0x24) ? 3 : (sp ? 2 : 3); double out = C.mpipe[depth - 1].v; if (dest && src1 == dest) v1 = out; if (dest && src2 == dest) v2 = out; retire(dest, C.mpipe[depth - 1]); padv(C.mpipe, v1 * v2, rp, depth, 0, 0); } else { wrf(dest, v1 * v2, rp); } return 0; } if (sub == 0x30 || sub == 0x31 || sub == 0x33) { /* fadd/fsub/famov */ double v1 = rdf(src1, sp), v2 = rdf(src2, sp); double res; if (pbit) { double out = C.apipe[2].v; if (dest && src1 == dest) v1 = out; if (dest && src2 == dest) v2 = out; res = (sub == 0x33) ? v1 : ((sub == 0x31) ? v1 - v2 : v1 + v2); retire(dest, C.apipe[2]); padv(C.apipe, res, rp, 3, 0, 0); } else { res = (sub == 0x33) ? v1 : ((sub == 0x31) ? v1 - v2 : v1 + v2); wrf(dest, res, rp); } return 0; } if (sub == 0x22) { /* frcp */ double b = rdf(src2, sp); wrf(dest, b != 0.0 ? 1.0 / b : 0.0, rp); return 0; } if (sub == 0x23) { /* frsqr */ double b = rdf(src2, sp); wrf(dest, b > 0.0 ? 1.0 / sqrt(b) : 0.0, rp); return 0; } if (sub == 0x32 || sub == 0x3a) { /* fix / ftrunc */ double a = rdf(src1, sp); uint64_t iv; if (a != a || a > 2147483647.0 || a < -2147483648.0) { iv = 0x80000000u; } else { double r_ = (sub == 0x3a) ? a : (a >= 0 ? a + 0.5 : a - 0.5); int64_t t_ = (int64_t)r_; iv = (uint64_t)t_; } iv &= rp ? 0xFFFFFFFFFFFFFFFFull : 0xFFFFFFFFull; if (pbit) { retire(dest, C.apipe[2]); padv(C.apipe, (double)(uint32_t)iv, rp, 3, 1, iv); } else if (rp) { int b0 = dest & ~1; fwr(b0, (uint32_t)iv); fwr(b0 | 1, (uint32_t)(iv >> 32)); } else { fwr(dest, (uint32_t)iv); } return 0; } if (sub == 0x34 || sub == 0x35) { /* pfgt/pfle (0x34) / pfeq (0x35) */ double v1 = rdf(src1, sp), v2 = rdf(src2, sp); if (sub == 0x35) set_cc(v1 == v2); else if (w & 0x80) set_cc(!(v1 <= v2)); else set_cc(v1 > v2); retire(dest, C.apipe[2]); padv(C.apipe, 0.0, sp, 3, 0, 0); return 0; } if (sub == 0x21) { /* fmlow.dd */ uint64_t prod = (uint64_t)frd(src1) * (uint64_t)frd(src2); int b0 = dest & ~1; fwr(b0, (uint32_t)prod); fwr(b0 | 1, (uint32_t)(prod >> 32)); return 0; } if (sub == 0x40) { /* fxfr FP->int */ wr(dest, frd(src1)); return 0; } if (sub == 0x49 || sub == 0x4d) { /* fiadd / fisub */ uint64_t a, b, m, r_; if (sp) { a = (uint64_t)frd(src1 & ~1) | ((uint64_t)frd((src1 & ~1) | 1) << 32); b = (uint64_t)frd(src2 & ~1) | ((uint64_t)frd((src2 & ~1) | 1) << 32); m = 0xFFFFFFFFFFFFFFFFull; } else { a = frd(src1); b = frd(src2); m = MASK32; } r_ = ((sub == 0x49) ? a + b : a - b) & m; if (pbit) { uint64_t out = C.gpipe; C.gpipe = (uint32_t)r_; /* NB python gpipe stores raw int */ r_ = out; } if (sp) { int b0 = dest & ~1; fwr(b0, (uint32_t)r_); fwr(b0 | 1, (uint32_t)(r_ >> 32)); } else { fwr(dest, (uint32_t)r_); } return 0; } if (sub == 0x5f) return 0; /* fnop */ C.stop = 1; snprintf(C.stopmsg, sizeof C.stopmsg, "unimplemented FP sub %#x @ %#x", sub, C.pc); return 1; } /* ---------------- the step loop ---------------- */ /* reasons: 0 = hit hook pc, 1 = stop, 2 = FP fallback (execute w at C.pc via * Python then call resume()), 3 = max steps */ static uint32_t *g_hooks; static int g_nhooks; static int is_hook(uint32_t pc) { for (int i = 0; i < g_nhooks; i++) if (g_hooks[i] == pc) return 1; return 0; } static int step_once(int *fp_fallback) { uint32_t pc = C.pc; uint32_t w = mem_r32(pc); int dim_at_fetch = C.dim; C.br_pending = 0; C.squash = 0; int rc = execute(w, pc); if (rc == 2) { *fp_fallback = 1; return 0; } /* pc unchanged; steps not counted */ C.steps++; dim_track(w, pc); if (C.stop) return 1; if (C.br_pending) { uint32_t target = C.br_target; if (C.br_delayed) { int nslots = dim_at_fetch ? 2 : 1; uint32_t ds = pc + 4; C.br_pending = 0; for (int k = 0; k < nslots; k++) { uint32_t w2 = mem_r32(ds); int rc2 = execute(w2, ds); if (rc2 == 2) { *fp_fallback = 2; C.pc = ds; return 0; } /* mid-slot fallback */ C.steps++; dim_track(w2, ds); ds += 4; } C.pc = C.br_pending ? C.br_target : target; C.dim_half = 0; } else { C.pc = target; C.dim_half = 0; } } else if (C.squash) { int nslots = dim_at_fetch ? 2 : 1; C.squash = 0; C.pc = pc + 4 + nslots * 4; } else { C.pc = pc + 4; } return C.stop; } /* ---------------- Python interface ---------------- */ static PyObject *py_init(PyObject *self, PyObject *args) { (void)self; (void)args; if (!g_pages) g_pages = (uint8_t **)calloc(N_PAGES, sizeof(uint8_t *)); memset(&C, 0, sizeof C); for (int i = 0; i < 3; i++) { C.apipe[i].rp = 1; C.mpipe[i].rp = 1; } Py_RETURN_NONE; } static PyObject *py_load_blob(PyObject *self, PyObject *args) { (void)self; Py_buffer buf; unsigned long long addr; if (!PyArg_ParseTuple(args, "Ky*", &addr, &buf)) return NULL; const uint8_t *src = (const uint8_t *)buf.buf; for (Py_ssize_t i = 0; i < buf.len; i++) mem_w8((uint32_t)(addr + i), src[i]); PyBuffer_Release(&buf); Py_RETURN_NONE; } static PyObject *py_set_hooks(PyObject *self, PyObject *args) { (void)self; PyObject *lst; if (!PyArg_ParseTuple(args, "O", &lst)) return NULL; Py_ssize_t n = PyList_Size(lst); free(g_hooks); g_hooks = (uint32_t *)malloc(sizeof(uint32_t) * (size_t)n); g_nhooks = (int)n; for (Py_ssize_t i = 0; i < n; i++) g_hooks[i] = (uint32_t)PyLong_AsUnsignedLong(PyList_GetItem(lst, i)); Py_RETURN_NONE; } static PyObject *py_run(PyObject *self, PyObject *args) { (void)self; unsigned long long max_steps; if (!PyArg_ParseTuple(args, "K", &max_steps)) return NULL; uint64_t start = C.steps; int reason = 3; while (C.steps - start < max_steps) { if (is_hook(C.pc) || C.pc == C.ret_sentinel) { reason = 0; break; } int fb = 0; int stopped = step_once(&fb); if (fb) { reason = 2; break; } if (stopped) { reason = 1; break; } } return Py_BuildValue("iK", reason, (unsigned long long)C.steps); } static PyObject *py_watch_add(PyObject *self, PyObject *args) { (void)self; unsigned long lo, hi; if (!PyArg_ParseTuple(args, "kk", &lo, &hi)) return NULL; if (g_nwatch < 8) { g_watch[g_nwatch][0] = (uint32_t)lo; g_watch[g_nwatch][1] = (uint32_t)hi; g_nwatch++; } Py_RETURN_NONE; } static PyObject *py_watch_drain(PyObject *self, PyObject *args) { (void)self; (void)args; PyObject *b = PyBytes_FromStringAndSize((const char *)g_wbuf, (Py_ssize_t)(g_wlen * 4)); g_wlen = 0; return b; } static PyObject *py_step1(PyObject *self, PyObject *args) { (void)self; (void)args; int fb = 0; int stopped = step_once(&fb); return Py_BuildValue("ii", stopped, fb); } static PyObject *py_getstate(PyObject *self, PyObject *args) { (void)self; (void)args; PyObject *regs = PyList_New(32), *fregs = PyList_New(32); for (int i = 0; i < 32; i++) { PyList_SetItem(regs, i, PyLong_FromUnsignedLong(C.r[i])); PyList_SetItem(fregs, i, PyLong_FromUnsignedLong(C.f[i])); } return Py_BuildValue("{s:N,s:N,s:k,s:K,s:i}", "r", regs, "f", fregs, "pc", C.pc, "steps", (unsigned long long)C.steps, "dim", C.dim); } static PyObject *py_setreg(PyObject *self, PyObject *args) { (void)self; int i; unsigned long v; int isf; if (!PyArg_ParseTuple(args, "iki", &i, &v, &isf)) return NULL; if (isf) C.f[i] = (uint32_t)v; else C.r[i] = (uint32_t)v; Py_RETURN_NONE; } static PyObject *py_setpc(PyObject *self, PyObject *args) { (void)self; unsigned long v; if (!PyArg_ParseTuple(args, "k", &v)) return NULL; C.pc = (uint32_t)v; Py_RETURN_NONE; } static PyObject *py_setsentinel(PyObject *self, PyObject *args) { (void)self; unsigned long v; if (!PyArg_ParseTuple(args, "k", &v)) return NULL; C.ret_sentinel = (uint32_t)v; Py_RETURN_NONE; } static PyObject *py_r32(PyObject *self, PyObject *args) { (void)self; unsigned long a; if (!PyArg_ParseTuple(args, "k", &a)) return NULL; return PyLong_FromUnsignedLong(mem_r32((uint32_t)a)); } static PyObject *py_w32(PyObject *self, PyObject *args) { (void)self; unsigned long a, v; if (!PyArg_ParseTuple(args, "kk", &a, &v)) return NULL; mem_w32((uint32_t)a, (uint32_t)v); Py_RETURN_NONE; } /* Bulk read [lo, hi) as bytes in ONE call -- replaces a Python-level r32() * loop (~24k calls for a typical per-draw program-window scan), which was * the dominant per-frame cost in the live render path. */ static PyObject *py_dump_range(PyObject *self, PyObject *args) { (void)self; unsigned long lo_, hi_; if (!PyArg_ParseTuple(args, "kk", &lo_, &hi_)) return NULL; uint32_t lo = (uint32_t)lo_, hi = (uint32_t)hi_; if (hi <= lo) return PyBytes_FromStringAndSize(NULL, 0); Py_ssize_t n = (Py_ssize_t)(hi - lo); PyObject *out = PyBytes_FromStringAndSize(NULL, n); if (!out) return NULL; uint8_t *dst = (uint8_t *)PyBytes_AS_STRING(out); memset(dst, 0, (size_t)n); uint32_t addr = lo; while (addr < hi) { uint32_t pi = addr >> PAGE_BITS; uint32_t off = addr & (PAGE_SIZE - 1); uint32_t take = PAGE_SIZE - off; if (take > hi - addr) take = hi - addr; uint8_t *p = g_pages[pi]; if (p) memcpy(dst + (addr - lo), p + off, take); addr += take; } return out; } static PyMethodDef methods[] = { {"init", py_init, METH_NOARGS, "reset state"}, {"load_blob", py_load_blob, METH_VARARGS, "load bytes at addr"}, {"set_hooks", py_set_hooks, METH_VARARGS, "set hook pcs"}, {"run", py_run, METH_VARARGS, "run until hook/stop/fallback/max"}, {"getstate", py_getstate, METH_NOARGS, "dump state"}, {"step1", py_step1, METH_NOARGS, "force one step (ignores hooks)"}, {"watch_add", py_watch_add, METH_VARARGS, "watch writes in [lo,hi)"}, {"watch_drain", py_watch_drain, METH_NOARGS, "drain the write log as bytes"}, {"setreg", py_setreg, METH_VARARGS, "set r/f register"}, {"setpc", py_setpc, METH_VARARGS, "set pc"}, {"set_sentinel", py_setsentinel, METH_VARARGS, "set return sentinel"}, {"r32", py_r32, METH_VARARGS, "read mem"}, {"w32", py_w32, METH_VARARGS, "write mem"}, {"dump_range", py_dump_range, METH_VARARGS, "bulk read [lo,hi) as bytes"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef mod = { PyModuleDef_HEAD_INIT, "emu860c", "i860 step-core in C", -1, methods, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_emu860c(void) { return PyModule_Create(&mod); }