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