shithub: apl10

Download patch

ref: 7b65afc1ad13f3859eca6eadaa2c45d864320304
author: Peter Mikkelsen <peter@pmikkelsen.com>
date: Fri Oct 25 14:07:26 EDT 2024

Initial commit

--- /dev/null
+++ b/as.c
@@ -1,0 +1,66 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "dat.h"
+
+static void _Noreturn
+usage(void)
+{
+	fprint(2, "usage: apl/as [-d] [-o outfile] [infile]\n");
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int disass = 0;
+	char *infile = nil;
+	char *outfile = nil;
+
+	Biobuf *in, *out;
+
+	ARGBEGIN{
+	case 'o':
+		outfile = EARGF(usage());
+		break;
+	case 'd':
+		disass = 1;
+		break;
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc > 1)
+		usage();
+	else if(argc == 1)
+		infile = *argv;
+
+	if(outfile == nil && infile != nil){
+		outfile = strdup(infile);
+		char *dot = utfrrune(outfile, '.');
+		char *inext = disass ? OC_EXT : BC_EXT;
+		char *outext = disass ? BC_EXT : OC_EXT; 
+		if(dot && strcmp(dot+1, inext) == 0)
+			dot[0] = 0;
+		outfile = smprint("%s.%s", outfile, outext);
+	}
+
+	in = infile ? Bopen(infile, OREAD) : Bfdopen(0, OREAD);
+	out = outfile ? Bopen(outfile, OWRITE|OTRUNC) : Bfdopen(1, OWRITE);
+	if(in == nil || out == nil)
+		sysfatal("open: %r");
+
+	Module *m = mallocz(sizeof(Module), 1);
+	for(int output = 0; output < 2; output++){
+		for(int n = 0; n < nelem(objparts); n++){
+			ObjpartSpec p = objparts[n];
+			if(output)
+				p.write(m, out, disass);
+			else
+				p.read(m, in, !disass);
+		}
+	}
+
+	Bterm(in);
+	Bterm(out);
+}
\ No newline at end of file
--- /dev/null
+++ b/comp.c
@@ -1,0 +1,9 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "dat.h"
+
+void
+main(void)
+{
+}
\ No newline at end of file
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,143 @@
+#define BC_EXT "aplbc"
+#define OC_EXT "aploc"
+
+enum {
+	Onop,
+	Oexit,
+	Ocall,
+	Oreturn,
+	Omov,
+	Olocals,
+	Oscalnum,
+	Odisplay,
+
+	O_max,
+	O_maxargs = 2,
+};
+
+enum {
+	OAinvalid,
+
+	OAlabel, /* 8 byte unsigned offset */
+	OAreg, /* 1 byte unsigned register number */
+	OAlocal1, /* 1 byte unsigned local number */
+	OAlocal2, /* 2 byte unsigned local number */
+	OAlocal4, /* 4 byte unsigned local number */
+	OAlocal8, /* 8 byte unsigned local number */
+	OAnum1, /* 1 signed byte */
+	OAnum2, /* 2 signed byte */
+	OAnum4, /* 4 signed byte */
+	OAnum8, /* 8 signed byte */
+
+	OA_maxbytes = 8,
+};
+
+enum {
+	ObjHeader,
+	ObjConsts,
+	ObjCode,
+
+	Obj_max
+};
+
+enum {
+	RegIp, /* instruction pointer */
+	RegMod, /* current module */
+	RegFunc, /* current function (so we know the names of variables) */
+	RegSp, /* top of stack */
+	RegFp, /* start of current stack frame */
+	RegX, /* left argument */
+	RegY, /* right argument */
+	RegF, /* left operand function */
+	RegG, /* right operand function */
+	RegR, /* result value */
+	RegT, /* bool result of test (cmp* instruction) */
+
+	Reg_max,
+	Reg_save = RegX /* all regs below are pushed */
+};
+char *regnames[Reg_max];
+
+typedef struct Label Label;
+typedef struct Module Module;
+typedef struct ObjpartSpec ObjpartSpec;
+typedef struct OpArg OpArg;
+typedef struct OpcodeSpec OpcodeSpec;
+typedef struct ParsedInstr ParsedInstr;
+typedef struct VM VM;
+typedef union  Word Word;
+
+union Word
+{
+	/* Add cases as they are needed */
+	void *vp;
+	char *cp;
+	u64int u64;
+	s64int s64;
+
+	s16int s16v[4];
+	s8int s8v[8];
+};
+
+struct Label
+{
+	char *name;
+	uvlong ioffset;
+	uvlong coffset;
+	uvlong nameoffset;
+};
+
+struct Module
+{
+	uvlong codesize;
+	u8int *code;
+
+	uvlong constsize;
+	u8int *consts;
+
+	/* stuff used for parsing */
+	uvlong nlabels;
+	uvlong ninstrs;
+	Label *labels;
+	ParsedInstr *instrs;
+};
+
+struct ObjpartSpec
+{
+	char *name;
+	void (*read)(Module *, Biobuf *, int);
+	void (*write)(Module *, Biobuf *, int);
+};
+extern ObjpartSpec objparts[Obj_max];
+
+struct OpArg
+{
+	int tag;
+	Word;
+};
+
+struct OpcodeSpec
+{
+	char *name;
+	int args;
+};
+extern OpcodeSpec optab[O_max];
+
+struct ParsedInstr
+{
+	int opcode;
+	OpArg args[O_maxargs];
+
+	uvlong len;
+	u8int buf[1 + 1 + (O_maxargs * OA_maxbytes)];
+};
+
+struct VM
+{
+	Word regs[Reg_max];
+
+	uvlong nmods;
+	Module **mods;
+
+	Word stack[1024]; /* FIXME: grow stack as needed */
+};
\ No newline at end of file
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,23 @@
+/* opcodes.c */
+void encodeinstr(ParsedInstr *, Label *, uvlong);
+void encodelabel(ParsedInstr *, Label *, uvlong);
+void decodeinstr(u8int *, ParsedInstr *, Label *, uvlong);
+
+/* util.c */
+int write1u(u8int *, u8int);
+int write2u(u8int *, u16int);
+int write4u(u8int *, u32int);
+int write8u(u8int *, u64int);
+int write1s(u8int *, s8int);
+int write2s(u8int *, s16int);
+int write4s(u8int *, s32int);
+int write8s(u8int *, s64int);
+
+u8int read1u(u8int *);
+u16int read2u(u8int *);
+u32int read4u(u8int *);
+u64int read8u(u8int *);
+s8int read1s(u8int *);
+s16int read2s(u8int *);
+s32int read4s(u8int *);
+s64int read8s(u8int *);
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,17 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/apl
+TARG=comp as vm
+HFILES=dat.h fns.h
+
+OFILES=\
+	objfile.$O\
+	opcodes.$O\
+	util.$O\
+
+</sys/src/cmd/mkmany
+
+install:
+	mkdir -p $BIN
+	for(cmd in $TARG)
+		mk $MKFLAGS $cmd.install
--- /dev/null
+++ b/objfile.c
@@ -1,0 +1,338 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ctype.h>
+#include "dat.h"
+#include "fns.h"
+
+static Rune
+nextrune(Biobuf *b)
+{
+	Rune r = Bgetrune(b);
+	Bungetrune(b);
+	return r;
+}
+
+static void
+skipspace(Biobuf *b)
+{
+	while(isspacerune(nextrune(b)))
+		Bgetrune(b);
+}
+
+static uvlong
+emitconst(Module *m, u8int *d, uvlong n)
+{
+	uvlong o = m->constsize;
+	m->constsize += n;
+	m->consts = realloc(m->consts, m->constsize);
+	memcpy(m->consts+o, d, n);
+	return o;
+}
+
+static void
+rheader(Module *m, Biobuf *b, int text)
+{
+	if(text){
+	}else{
+		u8int buf[8];
+
+		Bread(b, buf, sizeof(buf));
+		m->codesize = read8u(buf);
+		m->code = malloc(m->codesize);
+
+		Bread(b, buf, sizeof(buf));
+		m->constsize = read8u(buf);
+		m->consts = malloc(m->constsize);
+
+		Bread(b, buf, sizeof(buf));
+		m->nlabels = read8u(buf);
+		m->labels = malloc(sizeof(*m->labels) * m->nlabels);
+	}
+}
+
+static void
+wheader(Module *m, Biobuf *b, int text)
+{
+	if(text){
+	}else{
+		u8int buf[8];
+
+		write8u(buf, m->codesize);
+		Bwrite(b, buf, sizeof(buf));
+
+		write8u(buf, m->constsize);
+		Bwrite(b, buf, sizeof(buf));
+
+		write8u(buf, m->nlabels);
+		Bwrite(b, buf, sizeof(buf));
+	}
+}
+
+static void
+rconsts(Module *m, Biobuf *b, int text)
+{
+	if(text){
+	}else{
+		Bread(b, m->consts, m->constsize);
+	}
+}
+
+static void
+wconsts(Module *m, Biobuf *b, int text)
+{
+	if(text){
+	}else{
+		Bwrite(b, m->consts, m->constsize);
+	}
+}
+
+static OpArg
+roparg(char *s)
+{
+	OpArg arg;
+	arg.tag = OAinvalid;
+
+	char c = s[0];
+	if(c == '$'){ /* register */
+		for(int i = 0; i < Reg_max; i++){
+			if(strcmp(s+1, regnames[i]) == 0){
+				arg.tag = OAreg;
+				arg.u64 = i;
+				break;
+			}
+		}
+	}else if(c == '%'){ /* local variable/tmp */
+		char *r;
+		arg.u64 = strtoull(s+1, &r, 10);
+		if(*r == 0){
+			if(arg.u64 < (1<<8))
+				arg.tag = OAlocal1;
+			else if(arg.u64 < (1<<16))
+				arg.tag = OAlocal2;
+			else if(arg.u64 < ((uvlong)1<<32))
+				arg.tag = OAlocal4;
+			else
+				arg.tag = OAlocal8;
+		}
+	}else if(isalpha(c)){ /* label */
+		arg.tag = OAlabel;
+		for(char *p = s; *p; p++){
+			if(!isalnum(*p)){
+				arg.tag = OAinvalid;
+				break;
+			}
+		}
+		if(arg.tag != OAinvalid)
+			arg.cp = strdup(s);
+	}else{ /* number */
+		char *r;
+		arg.s64 = strtoll(s, &r, 0);
+		if(*r == 0){
+			/* TODO: deal with this, so it uses the smallest possible size */
+			arg.tag = OAnum8;
+		}
+	}
+
+	if(arg.tag == OAinvalid)
+		sysfatal("can't parse instruction operand: %s\n", s);
+	return arg;
+}
+
+static void
+rinstr(Module *m, char *iline)
+{
+	int ok = 0;
+
+	OpArg args[O_maxargs]; 
+	char *parts[O_maxargs+2]; /* one larger than it needs to be */
+	char *line = strdup(iline);
+
+	int n = getfields(line, parts, nelem(parts), 1, " \t");
+	if(n == 1 && parts[0][strlen(parts[0])-1] == ':'){
+		parts[0][strlen(parts[0])-1] = 0;
+		m->nlabels++;
+		m->labels = realloc(m->labels, sizeof(*m->labels) * m->nlabels);
+		m->labels[m->nlabels-1].name = strdup(parts[0]);
+		m->labels[m->nlabels-1].ioffset = m->ninstrs;
+		m->labels[m->nlabels-1].nameoffset = emitconst(m, (u8int*)(parts[0]), strlen(parts[0])+1);
+		ok = 1;
+	}else if(n > 0 && n < nelem(parts)){
+		n--;
+		for(int i = 1; i < n; i++){
+			if(parts[i][strlen(parts[i])-1] != ',')
+				goto end;
+			else{
+				parts[i][strlen(parts[i])-1] = 0;
+			
+			}
+		}
+		char *op = parts[0];
+		int opcode;
+		for(opcode = 0; opcode < O_max; opcode++)
+			if(strcmp(op, optab[opcode].name) == 0)
+				break;
+		if(opcode == O_max)
+			goto end;
+		if(n != optab[opcode].args)
+			sysfatal("'%s' instruction expected %d args, but got %d", op, optab[opcode].args, n);
+		for(int i = 0; i < n; i++)
+			args[i] = roparg(parts[i+1]);
+
+		m->ninstrs++;
+		m->instrs = realloc(m->instrs, sizeof(*m->instrs) * m->ninstrs);
+		memset(&m->instrs[m->ninstrs-1], 0, sizeof(*m->instrs));
+		m->instrs[m->ninstrs-1].opcode = opcode;
+		memcpy(m->instrs[m->ninstrs-1].args, args, sizeof(args));
+		ok = 1;
+	}
+end:
+	free(line);
+	if(!ok)
+		sysfatal("can't parse: %s\n", iline);
+	return;
+}
+
+static void
+fixlabels(Module *m, uvlong ioffset, uvlong coffset)
+{
+	for(int l = 0; l < m->nlabels; l++){
+		if(ioffset == m->labels[l].ioffset)
+			m->labels[l].coffset = coffset;
+	}
+}
+
+static void
+rcode(Module *m, Biobuf *b, int text)
+{
+	if(text){
+		int done = 0;
+		while(!done){
+			skipspace(b);
+			if(nextrune(b) == Beof)
+				break;
+			char *line = Brdstr(b, '\n', 1);
+			if(strlen(line) > 0)
+				rinstr(m, line);
+			else
+				done = 1;
+			free(line);
+		}
+
+		/* compute real label offsets and total code size */
+		uvlong offset = 0;
+		for(int i = 0; i < m->ninstrs; i++){
+			fixlabels(m, i, offset);
+			encodeinstr(&m->instrs[i], m->labels, m->nlabels);
+			offset += m->instrs[i].len;
+		}
+		fixlabels(m, m->ninstrs, offset);
+
+		m->codesize = offset;
+		m->code = mallocz(m->codesize, 1);
+		offset = 0;
+		for(int i = 0; i < m->ninstrs; i++){
+			ParsedInstr *p = &m->instrs[i];
+			encodelabel(p, m->labels, m->nlabels);
+			memcpy(m->code+offset, p->buf, p->len);
+			offset += p->len;
+		}
+	}else{
+		for(int i = 0; i < m->nlabels; i++){
+			u8int buf[8];
+			Bread(b, buf, sizeof(buf));
+			m->labels[i].coffset = read8u(buf);
+
+			Bread(b, buf, sizeof(buf));
+			m->labels[i].nameoffset = read8u(buf);
+			m->labels[i].name = strdup((char*)(m->consts+m->labels[i].nameoffset));
+		}
+		Bread(b, m->code, m->codesize);
+	}
+}
+
+static void
+woparg(Biobuf *b, OpArg *a)
+{
+	switch(a->tag){
+	case OAlabel:
+		Bprint(b, a->cp);
+		break;
+	case OAreg:
+		Bprint(b, "$%s", regnames[a->u64]);
+		break;
+	case OAlocal1:
+	case OAlocal2:
+	case OAlocal4:
+	case OAlocal8:
+		Bprint(b, "%%%ulld", a->u64);
+		break;
+	case OAnum1:
+	case OAnum2:
+	case OAnum4:
+	case OAnum8:
+		Bprint(b, "0x%llx", a->s64);
+		break;
+	}
+}
+
+static void
+winstr(Biobuf *b, ParsedInstr *p)
+{
+	Bprint(b, "\t%s", optab[p->opcode].name);
+	for(int i = 0; i < optab[p->opcode].args; i++){
+		if(i > 0)
+			Bprint(b, ",");
+		Bprint(b, " ");
+		woparg(b, &p->args[i]);
+	}
+	Bprint(b, "\n");
+}
+
+static void
+wlabel(Module *m, Biobuf *b, uvlong offset)
+{
+	for(int l = 0; l < m->nlabels; l++){
+		if(offset == m->labels[l].coffset)
+			Bprint(b, "%s:\n", m->labels[l].name);
+	}
+}
+
+static void
+wcode(Module *m, Biobuf *b, int text)
+{
+	if(text){
+		u8int *c = m->code;
+		while((c - m->code) < m->codesize){
+			m->ninstrs++;
+			m->instrs = realloc(m->instrs, m->ninstrs * sizeof(*m->instrs));
+			ParsedInstr *p = &m->instrs[m->ninstrs-1];
+			decodeinstr(c, p, m->labels, m->nlabels);
+			c += p->len;
+		}
+
+		uvlong offset = 0;
+		for(int i = 0; i < m->ninstrs; i++){
+			wlabel(m, b, offset);
+			winstr(b, &m->instrs[i]);
+			offset += m->instrs[i].len;
+		}
+		wlabel(m, b, offset);
+	}else{
+		for(int i = 0; i < m->nlabels; i++){
+			u8int buf[8];
+			write8u(buf, m->labels[i].coffset);
+			Bwrite(b, buf, sizeof(buf));
+
+			write8u(buf, m->labels[i].nameoffset);
+			Bwrite(b, buf, sizeof(buf));
+		}
+		Bwrite(b, m->code, m->codesize);
+	}
+}
+
+ObjpartSpec objparts[Obj_max] = {
+	[ObjHeader] = {"header", rheader, wheader},
+	[ObjConsts] = {"constants", rconsts, wconsts},
+	[ObjCode] = {"code", rcode, wcode},
+};
--- /dev/null
+++ b/opcodes.c
@@ -1,0 +1,188 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+
+char *regnames[Reg_max] = {
+	[RegIp] = "ip",
+	[RegMod] = "mod",
+	[RegFunc] = "func",
+	[RegSp] = "sp",
+	[RegFp] = "fp",
+	[RegX] = "x",
+	[RegY] = "y",
+	[RegF] = "f",
+	[RegG] = "g",
+	[RegR] = "r",
+	[RegT] = "t",
+};
+
+OpcodeSpec optab[O_max] = {
+	[Onop] = {"nop", 0},
+	[Oexit] = {"exit", 0},
+	[Ocall] = {"call", 1},
+	[Oreturn] = {"return", 0},
+	[Omov] = {"mov", 2},
+	[Olocals] = {"locals", 1},
+	[Oscalnum] = {"scalnum", 2},
+	[Odisplay] = {"display", 1},
+};
+
+void
+encodeinstr(ParsedInstr *p, Label *labels, uvlong nlabels)
+{
+	/* Encoding:
+	 *	1 byte opcode
+	 *	optional 1 byte argument info
+	 *	optional 1st arg
+	 *	optional 2nd arg
+	 */
+	u8int *d = p->buf;
+	*d++ = p->opcode;
+	if(optab[p->opcode].args > 0){
+		u8int info = 0;
+		info |= (p->args[0].tag & 0xF) << 0;
+		info |= (p->args[1].tag & 0xF) << 4;
+		*d++ = info;
+
+		int l;
+		for(int i = 0; i < optab[p->opcode].args; i++){
+			OpArg arg = p->args[i];
+			switch(arg.tag){
+			case OAlabel:
+				for(l = 0; l < nlabels; l++){
+					if(strcmp(arg.cp, labels[l].name) == 0){
+						d += write8u(d, labels[l].coffset);
+						break;
+					}
+				}
+				if(l == nlabels)
+					sysfatal("Undefined label %s", arg.cp);
+				break;
+			case OAreg:
+				d += write1u(d, arg.u64);
+				break;
+			case OAlocal1:
+				d += write1u(d, arg.u64);
+				break;
+			case OAlocal2:
+				d += write2u(d, arg.u64);
+				break;
+			case OAlocal4:
+				d += write4u(d, arg.u64);
+				break;
+			case OAlocal8:
+				d += write8u(d, arg.u64);
+				break;
+			case OAnum1:
+				d += write1s(d, arg.s64);
+				break;
+			case OAnum2:
+				d += write2s(d, arg.s64);
+				break;
+			case OAnum4:
+				d += write4s(d, arg.s64);
+				break;
+			case OAnum8:
+				d += write8s(d, arg.s64);
+				break;
+			default:
+				sysfatal("missing case in encodeinstr: %d", p->args[i].tag);
+			}
+		}
+	}
+	p->len = d - p->buf;
+}
+
+void
+encodelabel(ParsedInstr *p, Label *labels, uvlong nlabels)
+{
+	int islabel = 0;
+	for(int i = 0; i < optab[p->opcode].args; i++){
+		if(p->args[i].tag == OAlabel)
+			islabel = 1;
+	}
+	if(islabel)
+		encodeinstr(p, labels, nlabels);
+}
+
+void
+decodeinstr(u8int *d, ParsedInstr *p, Label *labels, uvlong nlabels)
+{
+	u8int *c = d;
+	int fast = (labels == nil);
+
+	p->opcode = *c++;
+	int args = optab[p->opcode].args;
+	if(args){
+		u8int info = *c++;
+		for(int i = 0; i < args; i++){
+			p->args[i].tag = info & 0xF;
+			info = info >> 4;
+
+			u64int u64;
+			int l;
+
+			switch(p->args[i].tag){
+			case OAlabel:
+				u64 = read8u(c);
+				c += 8;
+				if(fast){
+					p->args[i].u64 = u64;	
+				}else{
+					for(l = 0; l < nlabels; l++){
+						if(labels[l].coffset == u64)
+							break;
+					}
+					if(l == nlabels)
+						sysfatal("couldn't find label at offset %ulld", u64);
+					p->args[i].cp = strdup(labels[l].name);
+				}
+				break;
+			case OAreg:
+				p->args[i].u64 = read1u(c);
+				c += 1;
+				break;
+			case OAlocal1:
+				p->args[i].u64 = read1u(c);
+				c += 1;
+				break;
+			case OAlocal2:
+				p->args[i].u64 = read2u(c);
+				c += 2;
+				break;
+			case OAlocal4:
+				p->args[i].u64 = read4u(c);
+				c += 4;
+				break;
+			case OAlocal8:
+				p->args[i].u64 = read8u(c);
+				c += 8;
+				break;
+			case OAnum1:
+				p->args[i].s64 = read1s(c);
+				c += 1;
+				break;
+			case OAnum2:
+				p->args[i].s64 = read2s(c);
+				c += 2;
+				break;
+			case OAnum4:
+				p->args[i].s64 = read4s(c);
+				c += 4;
+				break;
+			case OAnum8:
+				p->args[i].s64 = read8s(c);
+				c += 8;
+				break;
+			default:
+				sysfatal("missing case in decodeinstr: %d", p->args[i].tag);
+			}
+		}
+	}
+
+	p->len = c - d;
+	if(!fast)
+		memcpy(p->buf, d, p->len);
+}
\ No newline at end of file
--- /dev/null
+++ b/test.aplbc
@@ -1,0 +1,33 @@
+diagonal:
+	locals 1
+	call iota
+	mov $r, %0
+	mov $r, $x
+	mov $r, $y
+	mov equals, $f
+	return
+
+equals:
+	return
+
+iota:
+	locals 1
+	getrank $y, %0
+	cmpeq 0, %0
+	jmpifnot .rankOK
+	error 4
+.rankOK:
+	return
+
+outerproduct:
+	return
+
+simpleTest:
+	scalnum 10, $y
+	call iota
+	display $r
+
+main:
+	call simpleTest
+	call diagonal
+	exit
--- /dev/null
+++ b/test.rc
@@ -1,0 +1,5 @@
+#!/bin/rc
+
+apl/as test.aplbc
+cat test.aploc | apl/as -d
+apl/vm test.aploc
--- /dev/null
+++ b/util.c
@@ -1,0 +1,173 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+write1u(u8int *d, u8int v)
+{
+	d[0] = v;
+	return 1;
+}
+
+int
+write2u(u8int *d, u16int v)
+{
+	d[0] = (v >> 8) & 0xFF;
+	d[1] = (v >> 0) & 0xFF;
+	return 2;
+}
+
+int
+write4u(u8int *d, u32int v)
+{
+	d[0] = (v >> 24) & 0xFF;
+	d[1] = (v >> 16) & 0xFF;
+	d[2] = (v >> 8) & 0xFF;
+	d[3] = (v >> 0) & 0xFF;
+	return 4;
+}
+
+int
+write8u(u8int *d, u64int v)
+{
+	d[0] = (v >> 56) & 0xFF;
+	d[1] = (v >> 48) & 0xFF;
+	d[2] = (v >> 40) & 0xFF;
+	d[3] = (v >> 32) & 0xFF;
+	d[4] = (v >> 24) & 0xFF;
+	d[5] = (v >> 16) & 0xFF;
+	d[6] = (v >> 8) & 0xFF;
+	d[7] = (v >> 0) & 0xFF;
+	return 8;
+}
+
+int
+write1s(u8int *d, s8int v)
+{
+	s8int *s = (s8int *)d;
+	s[0] = v;
+	return 1;
+}
+
+int
+write2s(u8int *d, s16int v)
+{
+	s8int *s = (s8int *)d;
+	s[0] = (v >> 8) & 0xFF;
+	s[1] = (v >> 0) & 0xFF;
+	return 2;
+}
+
+int
+write4s(u8int *d, s32int v)
+{
+	s8int *s = (s8int *)d;
+	s[0] = (v >> 24) & 0xFF;
+	s[1] = (v >> 16) & 0xFF;
+	s[2] = (v >> 8) & 0xFF;
+	s[3] = (v >> 0) & 0xFF;
+	return 4;
+}
+
+int
+write8s(u8int *d, s64int v)
+{
+	s8int *s = (s8int *)d;
+	s[0] = (v >> 56) & 0xFF;
+	s[1] = (v >> 48) & 0xFF;
+	s[2] = (v >> 40) & 0xFF;
+	s[3] = (v >> 32) & 0xFF;
+	s[4] = (v >> 24) & 0xFF;
+	s[5] = (v >> 16) & 0xFF;
+	s[6] = (v >> 8) & 0xFF;
+	s[7] = (v >> 0) & 0xFF;
+	return 8;
+}
+
+u8int
+read1u(u8int *d)
+{
+	u8int v = 0;
+	v |= ((u8int)d[0]) << 0;
+	return v;
+}
+
+u16int
+read2u(u8int *d)
+{
+	u16int v = 0;
+	v |= ((u16int)d[0]) << 8;
+	v |= ((u16int)d[1]) << 0;
+	return v;
+}
+
+u32int
+read4u(u8int *d)
+{
+	u32int v = 0;
+	v |= ((u32int)d[0]) << 24;
+	v |= ((u32int)d[1]) << 16;
+	v |= ((u32int)d[2]) << 8;
+	v |= ((u32int)d[3]) << 0;
+	return v;
+}
+
+u64int
+read8u(u8int *d)
+{
+	u64int v = 0;
+	v |= ((u64int)d[0]) << 56;
+	v |= ((u64int)d[1]) << 48;
+	v |= ((u64int)d[2]) << 40;
+	v |= ((u64int)d[3]) << 32;
+	v |= ((u64int)d[4]) << 24;
+	v |= ((u64int)d[5]) << 16;
+	v |= ((u64int)d[6]) << 8;
+	v |= ((u64int)d[7]) << 0;
+	return v;
+}
+
+s8int
+read1s(u8int *d)
+{
+	s8int v = 0;
+	v |= ((s8int)d[0]) << 0;
+	return v;
+}
+
+s16int
+read2s(u8int *d)
+{
+	s16int v = 0;
+	v |= ((s16int)d[0]) << 8;
+	v |= ((s16int)d[1]) << 0;
+	return v;
+}
+
+s32int
+read4s(u8int *d)
+{
+	s32int v = 0;
+	v |= ((s32int)d[0]) << 24;
+	v |= ((s32int)d[1]) << 16;
+	v |= ((s32int)d[2]) << 8;
+	v |= ((s32int)d[3]) << 0;
+	return v;
+}
+
+s64int
+read8s(u8int *d)
+{
+	s64int v = 0;
+	v |= ((s64int)d[0]) << 56;
+	v |= ((s64int)d[1]) << 48;
+	v |= ((s64int)d[2]) << 40;
+	v |= ((s64int)d[3]) << 32;
+	v |= ((s64int)d[4]) << 24;
+	v |= ((s64int)d[5]) << 16;
+	v |= ((s64int)d[6]) << 8;
+	v |= ((s64int)d[7]) << 0;
+	return v;
+}
\ No newline at end of file
--- /dev/null
+++ b/vm.c
@@ -1,0 +1,152 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "dat.h"
+#include "fns.h"
+
+static VM *vm;
+
+static void _Noreturn
+usage(void)
+{
+	fprint(2, "usage: apl/vm objfile\n");
+	exits("usage");
+}
+
+static void
+initvm(void)
+{
+	vm = mallocz(sizeof(VM), 1);
+}
+
+static void
+loadmod(char *file)
+{
+	Biobuf *b = Bopen(file, OREAD);
+	if(b == nil)
+		sysfatal("open: %r");
+
+	Module *m = mallocz(sizeof(Module), 1);
+	for(int n = 0; n < nelem(objparts); n++)
+		objparts[n].read(m, b, 0);
+	Bterm(b);
+
+	vm->nmods++;
+	vm->mods = realloc(vm->mods, vm->nmods * sizeof(*vm->mods));
+	vm->mods[vm->nmods-1] = m;
+}
+
+static void
+stackgrow(uvlong n)
+{
+	vm->regs[RegSp].u64 += n;
+	if(vm->regs[RegSp].u64 > nelem(vm->stack))
+		sysfatal("APL stack overflow");
+}
+
+static uvlong
+findlabel(char *name)
+{
+	Module *m = vm->mods[vm->regs[RegMod].u64];
+	for(int i = 0; i < m->nlabels; i++){
+		if(strcmp(name, m->labels[i].name) == 0)
+			return m->labels[i].coffset;
+	}
+	sysfatal("Failed to find label %s", name);
+}
+
+static Word *
+getaddr(OpArg *a)
+{
+	switch(a->tag){
+	case OAlabel:
+	case OAnum1:
+	case OAnum2:
+	case OAnum4:
+	case OAnum8:
+		return (Word*)&a->u64;
+	case OAreg:
+		return &vm->regs[a->u64];
+	case OAlocal1:
+	case OAlocal2:
+	case OAlocal4:
+	case OAlocal8:
+		if((a->u64+1) > (vm->regs[RegSp].u64 - vm->regs[RegFp].u64))
+			sysfatal("Use of unallocated local: %%%ulld\n", a->u64);
+		return &vm->stack[vm->regs[RegFp].u64+a->u64];
+	default:
+		sysfatal("unhandled case in getaddr: %d", a->tag);
+	}
+}
+
+static void
+interpret(void)
+{
+	ParsedInstr instr;
+
+	vm->regs[RegMod].u64 = 0;
+	u8int *code = vm->mods[vm->regs[RegMod].u64]->code;
+	vm->regs[RegIp].u64 = findlabel("main");
+
+	Word *src, *dst;
+	u64int sp;
+	for(;;){
+		decodeinstr(code + vm->regs[RegIp].u64, &instr, nil, 0);
+		vm->regs[RegIp].u64 += instr.len;
+
+		switch(instr.opcode){
+		case Onop:
+			break;
+		case Oexit:
+			exits(nil);
+			break;
+		case Ocall:
+			sp = vm->regs[RegSp].u64;
+			stackgrow(Reg_save);
+			memcpy(vm->stack+sp, vm->regs, Reg_save * sizeof(*vm->regs));
+			vm->regs[RegIp].u64 = instr.args[0].u64;
+			vm->regs[RegFp].u64 = vm->regs[RegSp].u64;
+			break;
+		case Oreturn:
+			if(vm->regs[RegFp].u64 == 0)
+				sysfatal("APL stack underflow");
+
+			sp = vm->regs[RegFp].u64 - Reg_save;
+			memcpy(vm->regs, vm->stack + sp, Reg_save * sizeof(*vm->regs));
+			vm->regs[RegSp].u64 = sp;
+			break;
+		case Omov:
+			src = getaddr(&instr.args[0]);
+			dst = getaddr(&instr.args[1]);
+			memcpy(dst, src, sizeof(Word));
+			break;
+		case Olocals:
+			stackgrow(instr.args[0].s64);
+			break;
+		case Oscalnum:
+		case Odisplay:
+		default:
+			sysfatal("missing case in interpret: %s", optab[instr.opcode].name);
+		}
+	}
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *objfile;
+
+	ARGBEGIN{
+	default:
+		usage();
+	}ARGEND;
+
+	if(argc != 1)
+		usage();
+	objfile = *argv;
+
+	initvm();
+	loadmod(objfile);
+
+	interpret();
+}
\ No newline at end of file