| git.druid.rocks | index | druid520 | jury | src/ | jury.c |
src/jury.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "isa.h"
#define F_Z 1
#define F_C 2
#define F_N 4
typedef struct {
unsigned char ram[RAM_SIZE];
unsigned char r[NREGS];
unsigned int sp;
unsigned int pc;
unsigned char flags;
int running;
const char* halt;
long lastst; /* last address ST/STR wrote, -1 if none yet */
ins in; /* the instruction being run */
} vm;
typedef struct {
int id;
void (*fn)(vm* m);
} handler;
void vminit(vm* m);
void vmload(vm* m, const unsigned char* prog, int len);
void vmstep(vm* m);
void vmdump(vm* m);
void vmtrace(vm* m);
int main(int argc, char** argv);
/* ---- machine state helpers ---- */
static void
stop(vm* m, const char* why)
{
m->halt = why;
m->running = 0;
}
/* the register named by the low nibble */
static unsigned char*
reg(vm* m)
{
return &m->r[m->in.nib];
}
/* the x:y register pair as a 16 bit value */
static unsigned int
pairget(vm* m)
{
return ((unsigned int)m->r[m->in.x] << 8) | m->r[m->in.y];
}
static void
pairset(vm* m, unsigned int v)
{
m->r[m->in.x] = (v >> 8) & 0xFF;
m->r[m->in.y] = v & 0xFF;
}
static int
carry(vm* m)
{
return (m->flags & F_C) != 0;
}
/* ---- flags ---- */
/* z and n off an 8 bit value, c left alone */
static void
flagszn(vm* m, unsigned int v)
{
m->flags = m->flags & F_C;
if((v & 0xFF) == 0)
{
m->flags = m->flags | F_Z;
}
if((v & 0x80) != 0)
{
m->flags = m->flags | F_N;
}
}
static void
flagsc(vm* m, int c)
{
m->flags = m->flags & ~F_C;
if(c != 0)
{
m->flags = m->flags | F_C;
}
}
/* result is the untruncated int, c means it didnt fit in a byte */
static unsigned char
add8(vm* m, int a, int b, int cin)
{
int r = a + b + cin;
flagszn(m, r);
flagsc(m, r > 0xFF);
return r & 0xFF;
}
static unsigned char
sub8(vm* m, int a, int b, int bin)
{
int r = a - b - bin;
flagszn(m, r);
flagsc(m, r < 0);
return r & 0xFF;
}
static unsigned char
logic8(vm* m, int v)
{
flagszn(m, v);
return v & 0xFF;
}
/* ---- memory and stack ---- */
static int
addrok(vm* m, unsigned int a)
{
if(a >= RAM_SIZE)
{
stop(m, "bad address.");
return 0;
}
return 1;
}
static void
load(vm* m, unsigned int a, unsigned char* dst)
{
if(addrok(m, a) != 0)
{
*dst = m->ram[a];
}
}
static void
store(vm* m, unsigned int a, unsigned char v)
{
if(addrok(m, a) != 0)
{
m->ram[a] = v;
m->lastst = (long)a;
}
}
/* the stack grows down from STACK_TOP. sp points at the last byte
* pushed, so an empty stack has sp == STACK_TOP. every push/pop checks
* room for all its bytes before touching anything. */
static int
stackroom(vm* m, unsigned int n)
{
if(m->sp - STACK_BASE < n)
{
stop(m, "stack overflow.");
return 0;
}
return 1;
}
static int
stackhas(vm* m, unsigned int n)
{
if(STACK_TOP - m->sp < n)
{
stop(m, "stack underflow.");
return 0;
}
return 1;
}
static void
push8(vm* m, unsigned char v)
{
m->sp = m->sp - 1;
m->ram[m->sp] = v;
}
static unsigned char
pop8(vm* m)
{
m->sp = m->sp + 1;
return m->ram[m->sp - 1];
}
/* return address goes on big endian, same as everywhere else */
static void
call(vm* m, unsigned int to)
{
if(stackroom(m, 2) != 0)
{
push8(m, m->pc & 0xFF);
push8(m, (m->pc >> 8) & 0xFF);
m->pc = to;
}
}
/* ---- normal ops ---- */
static void op_halt(vm* m) { m->running = 0; }
static void op_ldi(vm* m) { *reg(m) = m->in.val; }
static void op_add(vm* m) { *reg(m) = add8(m, *reg(m), m->in.val, 0); }
static void op_sub(vm* m) { *reg(m) = sub8(m, *reg(m), m->in.val, 0); }
static void op_and(vm* m) { *reg(m) = logic8(m, *reg(m) & m->in.val); }
static void op_or(vm* m) { *reg(m) = logic8(m, *reg(m) | m->in.val); }
static void op_xor(vm* m) { *reg(m) = logic8(m, *reg(m) ^ m->in.val); }
static void op_cmp(vm* m) { sub8(m, *reg(m), m->in.val, 0); }
static void op_ld(vm* m) { load(m, m->in.val, reg(m)); }
static void op_st(vm* m) { store(m, m->in.val, *reg(m)); }
static void op_call(vm* m) { call(m, m->in.val); }
static void op_mode(vm* m) { stop(m, "bad opcode."); } /* decoder never lets this through */
static void
op_jcc(vm* m)
{
int f = m->flags;
int go = 0;
switch(m->in.nib)
{
case CC_ALWAYS: go = 1; break;
case CC_Z: go = (f & F_Z) != 0; break;
case CC_NZ: go = (f & F_Z) == 0; break;
case CC_C: go = (f & F_C) != 0; break;
case CC_NC: go = (f & F_C) == 0; break;
case CC_N: go = (f & F_N) != 0; break;
case CC_NN: go = (f & F_N) == 0; break;
}
if(go != 0)
{
m->pc = m->in.val;
}
}
static void
op_push(vm* m)
{
if(stackroom(m, 1) != 0)
{
push8(m, *reg(m));
}
}
static void
op_pop(vm* m)
{
if(stackhas(m, 1) != 0)
{
*reg(m) = pop8(m);
}
}
static void
op_ret(vm* m)
{
unsigned int hi;
if(stackhas(m, 2) != 0)
{
hi = pop8(m);
m->pc = (hi << 8) | pop8(m);
}
}
/* ---- mode 1, through a register pair ---- */
static void op_ldr(vm* m) { load(m, pairget(m), reg(m)); }
static void op_str(vm* m) { store(m, pairget(m), *reg(m)); }
static void op_jmpr(vm* m) { m->pc = pairget(m); }
static void op_callr(vm* m) { call(m, pairget(m)); }
/* 16 bit: z if the pair hit 0, n off bit 15, c if it wrapped */
static void
pairstep(vm* m, long d)
{
long r = (long)pairget(m) + d;
m->flags = 0;
if((r & 0xFFFF) == 0)
{
m->flags = m->flags | F_Z;
}
if((r & 0x8000) != 0)
{
m->flags = m->flags | F_N;
}
if(r < 0 || r > 0xFFFF)
{
m->flags = m->flags | F_C;
}
pairset(m, (unsigned int)(r & 0xFFFF));
}
static void op_incp(vm* m) { pairstep(m, 1); }
static void op_decp(vm* m) { pairstep(m, -1); }
/* ---- mode 2, one register ---- */
static void
shift(vm* m, int left, int cin)
{
unsigned char v = *reg(m);
int cout;
if(left != 0)
{
cout = (v >> 7) & 1;
v = ((v << 1) | cin) & 0xFF;
}
else
{
cout = v & 1;
v = ((v >> 1) | (cin << 7)) & 0xFF;
}
*reg(m) = v;
flagszn(m, v);
flagsc(m, cout);
}
static void op_shl(vm* m) { shift(m, 1, 0); }
static void op_shr(vm* m) { shift(m, 0, 0); }
static void op_rol(vm* m) { shift(m, 1, carry(m)); }
static void op_ror(vm* m) { shift(m, 0, carry(m)); }
static void op_not(vm* m) { *reg(m) = logic8(m, ~*reg(m)); }
static void op_neg(vm* m) { *reg(m) = sub8(m, 0, *reg(m), 0); }
static void op_inc(vm* m) { *reg(m) = add8(m, *reg(m), 1, 0); }
static void op_dec(vm* m) { *reg(m) = sub8(m, *reg(m), 1, 0); }
static void op_out(vm* m) { printf("%02x\n", *reg(m)); }
static void op_outc(vm* m) { putchar(*reg(m)); }
/* ---- mode 3, register/register, x is dest y is source (written "y, x") ---- */
#define RX (m->r[m->in.x])
#define RY (m->r[m->in.y])
static void op_mov(vm* m) { RX = RY; }
static void op_addr(vm* m) { RX = add8(m, RX, RY, 0); }
static void op_adcr(vm* m) { RX = add8(m, RX, RY, carry(m)); }
static void op_subr(vm* m) { RX = sub8(m, RX, RY, 0); }
static void op_sbcr(vm* m) { RX = sub8(m, RX, RY, carry(m)); }
static void op_cmpr(vm* m) { sub8(m, RX, RY, 0); }
static void op_andr(vm* m) { RX = logic8(m, RX & RY); }
static void op_orr(vm* m) { RX = logic8(m, RX | RY); }
static void op_xorr(vm* m) { RX = logic8(m, RX ^ RY); }
static void
op_swap(vm* m)
{
unsigned char t = RX;
RX = RY;
RY = t;
}
#undef RX
#undef RY
/* ---- mode 4, more register/immediate ---- */
static void op_adc(vm* m) { *reg(m) = add8(m, *reg(m), m->in.val, carry(m)); }
static void op_sbc(vm* m) { *reg(m) = sub8(m, *reg(m), m->in.val, carry(m)); }
static void op_tst(vm* m) { logic8(m, *reg(m) & m->in.val); }
/* ---- dispatch, indexed by op id. order has to match the enum in
* isa.h, main checks that before running anything. ---- */
static const handler EXEC[OP_COUNT] =
{
{OP_HALT, op_halt}, {OP_LDI, op_ldi}, {OP_ADD, op_add},
{OP_SUB, op_sub}, {OP_AND, op_and}, {OP_OR, op_or},
{OP_XOR, op_xor}, {OP_CMP, op_cmp}, {OP_JCC, op_jcc},
{OP_LD, op_ld}, {OP_ST, op_st}, {OP_CALL, op_call},
{OP_MODE, op_mode}, {OP_PUSH, op_push}, {OP_POP, op_pop},
{OP_RET, op_ret},
{OP_LDR, op_ldr}, {OP_STR, op_str}, {OP_JMPR, op_jmpr},
{OP_CALLR, op_callr}, {OP_INCP, op_incp}, {OP_DECP, op_decp},
{OP_SHL, op_shl}, {OP_SHR, op_shr}, {OP_ROL, op_rol},
{OP_ROR, op_ror}, {OP_NOT, op_not}, {OP_NEG, op_neg},
{OP_INC, op_inc}, {OP_DEC, op_dec}, {OP_OUT, op_out},
{OP_OUTC, op_outc},
{OP_MOV, op_mov}, {OP_SWAP, op_swap}, {OP_ADDR, op_addr},
{OP_ADCR, op_adcr}, {OP_SUBR, op_subr}, {OP_SBCR, op_sbcr},
{OP_CMPR, op_cmpr}, {OP_ANDR, op_andr}, {OP_ORR, op_orr},
{OP_XORR, op_xorr},
{OP_ADC, op_adc}, {OP_SBC, op_sbc}, {OP_TST, op_tst}
};
/* ---- the vm ---- */
void
vminit(vm* m)
{
memset(m, 0, sizeof(*m));
m->sp = STACK_TOP;
m->running = 1;
m->halt = "ok.";
m->lastst = -1;
}
void
vmload(vm* m, const unsigned char* prog, int len)
{
memcpy(m->ram, prog, len);
}
void
vmstep(vm* m)
{
int st = isadecode(m->ram, RAM_SIZE, m->pc, &m->in);
if(st != DEC_OK)
{
stop(m, DECMSG[st]);
return;
}
m->pc = m->pc + m->in.len;
EXEC[m->in.def->id].fn(m);
}
/* ---- looking at it ---- */
static void
flagstr(vm* m, char* buf)
{
buf[0] = (m->flags & F_Z) != 0 ? 'z' : '-';
buf[1] = (m->flags & F_C) != 0 ? 'c' : '-';
buf[2] = (m->flags & F_N) != 0 ? 'n' : '-';
buf[3] = '\0';
}
/* "c3 20 12 ADDR r2, r1" for the instruction at a */
static void
insstr(vm* m, unsigned int a, char* buf)
{
ins in;
char txt[INS_TXT];
char hex[INS_MAX * 3 + 1];
int st;
int i;
st = isadecode(m->ram, RAM_SIZE, a, &in);
if(st != DEC_OK)
{
if(a < RAM_SIZE)
{
sprintf(buf, "%02x ?? %s", m->ram[a], DECMSG[st]);
}
else
{
sprintf(buf, " ?? %s", DECMSG[st]);
}
return;
}
hex[0] = '\0';
for(i = 0; i < in.len; i = i + 1)
{
sprintf(hex + i * 3, "%02x ", m->ram[a + i]);
}
isafmt(&in, txt);
sprintf(buf, "%-12s %s", hex, txt);
}
/* 16 bytes of ram from the row holding a, mk marks one byte */
static void
dumprow(vm* m, const char* tag, unsigned int a, unsigned int mk, int mc)
{
unsigned int row = a & ~0xFu;
unsigned int i;
printf("%-6s %04x:", tag, row);
for(i = row; i < row + 16 && i < RAM_SIZE; i = i + 1)
{
printf("%c%02x", i == mk ? mc : ' ', m->ram[i]);
}
printf("\n");
}
void
vmdump(vm* m)
{
char fl[4];
char txt[64];
unsigned int i;
int k;
flagstr(m, fl);
printf("pc=%04x sp=%04x flags=%s ", m->pc, m->sp, fl);
for(k = 0; k < NREGS; k = k + 1)
{
printf(" r%d=%02x", k, m->r[k]);
}
printf("\n");
insstr(m, m->pc, txt);
printf("next %04x: %s\n", m->pc, txt);
/* the row pc is in and the one after it, pc marked with > */
if(m->pc < RAM_SIZE)
{
dumprow(m, "ram", m->pc, m->pc, '>');
if((m->pc | 0xF) + 1 < RAM_SIZE)
{
dumprow(m, "", (m->pc | 0xF) + 1, m->pc, '>');
}
}
/* stack top first, at most 16 bytes of it */
if(m->sp == STACK_TOP)
{
printf("stack empty\n");
}
else
{
printf("stack %04x:", m->sp);
for(i = m->sp; i < STACK_TOP && i < m->sp + 16; i = i + 1)
{
printf(" %02x", m->ram[i]);
}
printf("%s (%u/%d)\n", STACK_TOP - m->sp > 16 ? " .." : "", STACK_TOP - m->sp, STACK_SIZE);
}
/* where the last store went, marked with * */
if(m->lastst >= 0)
{
dumprow(m, "stored", (unsigned int)m->lastst, (unsigned int)m->lastst, '*');
}
}
/* one line per step for -r */
void
vmtrace(vm* m)
{
char fl[4];
char txt[64];
int k;
flagstr(m, fl);
insstr(m, m->pc, txt);
printf("%04x %-32s", m->pc, txt);
for(k = 0; k < NREGS; k = k + 1)
{
printf(" r%d=%02x", k, m->r[k]);
}
printf(" sp=%04x %s\n", m->sp, fl);
}
/* default (no flag): straight through, no output but [start]/[stop] --
* quiet is the plain thing to do with a program you already trust,
* dumping every step is the exception you have to ask for. -r traces
* one line per step; -d single-steps and shows the full state (regs,
* ram window, stack) each time, pausing for enter -- what running
* with no flag used to do unconditionally, before dumping had a flag
* of its own to hide behind. */
static int
usage(const char* me)
{
fprintf(stderr, "err: bad usage.\n");
fprintf(stderr, "usage: %s prog.bin [-r|-d]\n", me);
return 1;
}
int
main(int argc, char** argv)
{
static vm m;
static unsigned char prog[RAM_SIZE + 1];
FILE* f;
int len;
int run = 0;
int k;
for(k = 0; k < OP_COUNT; k = k + 1)
{
if(EXEC[k].id != k)
{
fprintf(stderr, "err: EXEC table out of order at %d.\n", k);
return 1;
}
}
if(argc < 2 || argc > 3)
{
return usage(argv[0]);
}
if(argc == 3)
{
if(strcmp(argv[2], "-r") == 0)
{
run = 1;
}
else if(strcmp(argv[2], "-d") == 0)
{
run = 2;
}
else
{
return usage(argv[0]);
}
}
f = fopen(argv[1], "rb");
if(f == NULL)
{
perror("fopen");
return 1;
}
len = fread(prog, 1, RAM_SIZE + 1, f);
fclose(f);
if(len > RAM_SIZE)
{
fprintf(stderr, "err: %s is bigger than ram (%d bytes).\n", argv[1], RAM_SIZE);
return 1;
}
vminit(&m);
vmload(&m, prog, len);
printf("[start]\n");
while(m.running != 0)
{
if(run == 2)
{
vmdump(&m);
}
if(run == 1)
{
vmtrace(&m);
}
vmstep(&m);
if(run == 2)
{
getchar();
}
}
if(run == 2)
{
vmdump(&m);
}
printf("[stop] %s\n", m.halt);
return strcmp(m.halt, "ok.") == 0 ? 0 : 1;
}