commit acdd1856e9e051422946a7d675dd5b727cc1cece
parent b11bf44c2de78b2b425840a0dc9203420aaf4b07
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Wed, 3 Aug 2022 10:14:06 -0400
Dusk CVM: first steps
Diffstat:
3 files changed, 584 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,4 +1,5 @@
/dusk
+/duskvm
/fatfs
/boot.fs
/fs/init.fs
diff --git a/Makefile b/Makefile
@@ -12,6 +12,9 @@ dusk: dusk.asm boot.fs fatfs
nasm -f elf32 dusk.asm -o dusk.o
ld -m elf_i386 dusk.o -o $@
+duskvm: posix/vm.c
+ $(CC) posix/vm.c -Wall -o $@
+
boot.fs: $(BOOTFS_SRC)
cat $(BOOTFS_SRC) > $@
diff --git a/posix/vm.c b/posix/vm.c
@@ -0,0 +1,580 @@
+/* VM for bootstrapping Dusk from POSIX platforms
+
+This is a Forth VM that has the goal of being able to bootstrap Dusk from any
+POSIX platform. It is limited compared to bare-metal builds because it cannot
+run CC, but it still has the ability to load assemblers and thus create bare
+metal builds.
+
+The VM is little endian.
+*/
+#include <inttypes.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#define MEMSZ 0x100000 // 1MB
+#define STACKSZ 0x800
+#define RSTOP MEMSZ
+#define PSTOP RSTOP-STACKSZ
+#define SYSVARSSZ 0x80
+#define SYSVARS (PSTOP-STACKSZ)-SYSVARSSZ
+#define HERE SYSVARS
+#define CURRENT HERE+4
+#define COMPILING CURRENT+4
+#define CURWORD COMPILING+4
+#define HEREMAX SYSVARS
+
+
+typedef uint8_t byte;
+typedef uint16_t word;
+typedef uint32_t dword;
+
+struct VM {
+ dword PSP;
+ dword RSP;
+ dword PC; // when PC >= MEMSZ, machine is halted
+ dword toptr;
+ dword bootptr;
+ dword areg;
+ byte mem[MEMSZ];
+};
+
+// Various labels set during dict building phase
+static dword lblmain = 0;
+
+static struct VM vm = {0};
+
+// Utilities
+static byte gb(dword addr) { return vm.mem[addr]; }
+static word gw(dword addr) { return gb(addr) | (gb(addr+1)<<8); }
+static dword gd(dword addr) { return gw(addr) | (gw(addr+2)<<16); }
+static dword gpc() { dword n = gd(vm.PC); vm.PC += 4; return n; }
+static void sb(dword addr, byte b) { vm.mem[addr] = b; }
+static void sw(dword addr, word w) { sb(addr, w); sb(addr+1, w>>8); }
+static void sd(dword addr, dword d) { sw(addr, d); sw(addr+1, d>>16); }
+static dword ppeek() { return gd(vm.PSP); }
+static dword ppop() { dword n = ppeek(); vm.PSP += 4; return n; }
+static void ppush(dword d) { vm.PSP -= 4; sd(vm.PSP, d); }
+static dword rpeek() { return gd(vm.RSP); }
+static dword rpop() { dword n = rpeek(); vm.RSP += 4; return n; }
+static void rpush(dword d) { vm.RSP -= 4; sd(vm.RSP, d); }
+static dword here() { return gd(HERE); }
+static void allot(dword n) { sd(HERE, here()+n); }
+static dword current() { return gd(CURRENT); }
+static dword find(char* name) {
+ byte slen = strlen(name);
+ dword a = current();
+ byte len;
+ while (a) {
+ len = gb(a-1) & 0x3f;
+ if ((len == slen) && (memcmp(name, &vm.mem[a-5-len], len)==0)) {
+ return a;
+ }
+ a = gd(a-5);
+ }
+ return 0;
+}
+
+// Operations
+/* The VM works in a simple manner. PC starts at 0. The VM reads the byte
+where PC points to in memory, increases PC by 1 and executes the corresponding
+function in the function list below. As simple as this. */
+
+static void BR() { // op: 00
+ vm.PC = gpc();
+}
+
+static void CALL() { // op: 01
+ dword n = gpc();
+ rpush(vm.PC);
+ vm.PC = n;
+}
+
+static void RET() { // op: 02
+ vm.PC = rpop();
+}
+
+static void LIT() { // op: 03
+ dword n = gpc();
+ ppush(n);
+ // ppush(gpc());
+}
+
+static void BYE() { // op: 04
+ vm.PC = MEMSZ;
+}
+
+static void BYEFAIL() { // op: 05
+ vm.PC = MEMSZ + 1;
+}
+
+static void QUIT() { // op: 06
+ vm.toptr = 0;
+ vm.RSP = RSTOP;
+ vm.PC = lblmain;
+}
+
+static void ABORT() { // op: 07
+ vm.PSP = PSTOP;
+ QUIT();
+}
+
+static void EXECUTE() { // op: 08
+ vm.PC = ppop();
+}
+
+static void CELL() { // op: 09
+ ppush(rpop());
+}
+
+static void VAL() { // op: 0a
+ dword a = rpop();
+ if (vm.toptr) {
+ ppush(a);
+ vm.PC = vm.toptr;
+ vm.toptr = 0;
+ } else {
+ ppush(gd(a));
+ }
+}
+
+static void ALIAS() { // op: 0b
+ dword a = rpop();
+ if (vm.toptr) {
+ ppush(a);
+ vm.PC = vm.toptr;
+ vm.toptr = 0;
+ } else {
+ vm.PC = gd(a);
+ }
+}
+
+static void DOES() { // op: 0c
+ dword a = rpop();
+ ppush(a+4);
+ vm.PC = gd(a);
+}
+
+static void SLIT() { // op: 0d
+ dword a = rpop();
+ ppush(a+1);
+ ppush(gd(a));
+}
+
+static void CBR() { // op: 0e
+ if (ppop()) {
+ vm.PC += 4;
+ } else {
+ BR();
+ }
+}
+
+static void NEXT() { // op: 0f
+ dword n = rpop();
+ if (--n) {
+ rpush(n);
+ BR();
+ } else {
+ vm.PC += 4;
+ }
+}
+
+static void BOOTRD() { // op: 10
+ ppush(gb(vm.bootptr++));
+}
+
+static void EMIT() { // op: 11
+ putc(ppop(), stdout);
+}
+
+static void STDERR() { // op: 12
+ putc(ppop(), stderr);
+}
+
+static void KEY() { // op: 13
+ ppush(getc(stdin));
+}
+
+static void DROP() { // op: 14
+ ppop();
+}
+
+static void DUP() { // op: 15
+ ppush(ppeek());
+}
+
+static void CDUP() { // op: 16
+ if (ppeek()) { DUP(); }
+}
+
+static void SWAP() { // op: 17
+ dword a = ppop();
+ dword b = ppop();
+ ppush(a); ppush(b);
+}
+
+static void OVER() { // op: 18
+ dword a = gd(vm.PSP+4);
+ ppush(a);
+}
+
+static void ROT() { // op: 19
+ dword a = ppop();
+ dword b = ppop();
+ dword c = ppop();
+ ppush(b); ppush(a); ppush(c);
+}
+
+static void ROTR() { // op: 1a
+ dword a = ppop();
+ dword b = ppop();
+ dword c = ppop();
+ ppush(a); ppush(c); ppush(b);
+}
+
+static void NIP() { // op: 1b
+ dword n = ppop();
+ sd(vm.PSP, n);
+}
+
+static void TUCK() { // op: 1c
+ dword a = ppop();
+ dword b = ppop();
+ ppush(a); ppush(b); ppush(a);
+}
+
+/* Warning: RS routines are all called, which means that we have to work from
+the second item from the top rather than the first. */
+
+static void RS2PS() { // op: 1d
+ dword a = rpop();
+ ppush(rpop());
+ rpush(a);
+}
+
+static void PS2RS() { // op: 1e
+ dword a = rpop();
+ rpush(ppop());
+ rpush(a);
+}
+
+static void RSGET() { // op: 1f
+ ppush(gd(vm.RSP+4));
+}
+
+static void RDROP() { // op: 20
+ dword a = rpop();
+ ppop();
+ rpush(a);
+}
+
+static void SCNT() { // op: 21
+ ppush((PSTOP-vm.PSP)>>2);
+}
+
+static void RCNT() { // op: 22
+ ppush(((RSTOP-vm.RSP)>>2)-1);
+}
+
+static void ASET() { // op: 23
+ vm.areg = ppop();
+}
+
+static void AGET() { // op: 24
+ ppush(vm.areg);
+}
+
+static void ACFETCH() { // op: 25
+ ppush(gb(vm.areg));
+}
+
+static void ACSTORE() { // op: 26
+ sb(vm.areg, ppop());
+}
+
+static void AINC() { // op: 27
+ vm.areg++;
+}
+
+static void ADEC() { // op: 28
+ vm.areg--;
+}
+
+static void A2RS() { // op: 29
+ rpush(vm.areg);
+}
+
+static void RS2A() { // op: 2a
+ vm.areg = rpop();
+}
+
+static void TOSET() { // op: 2b
+ vm.toptr = ppop();
+}
+
+static void TOGET() { // op: 2c
+ ppush(vm.toptr);
+ vm.toptr = 0;
+}
+
+static void INC() { // op: 2d
+ ppush(ppop()+1);
+}
+
+static void DEC() { // op: 2e
+ ppush(ppop()-1);
+}
+
+static void CFETCH() { // op: 2f
+ ppush(gb(ppop()));
+}
+
+static void CSTORE() { // op: 30
+ dword a = ppop();
+ sb(a, ppop());
+}
+
+static void CWRITE() { // op: 31
+ sb(here(), ppop());
+ allot(1);
+}
+
+static void WFETCH() { // op: 32
+ ppush(gw(ppop()));
+}
+
+static void WSTORE() { // op: 33
+ dword a = ppop();
+ sw(a, ppop());
+}
+
+static void FETCH() { // op: 34
+ ppush(gd(ppop()));
+}
+
+static void STORE() { // op: 35
+ dword a = ppop();
+ sd(a, ppop());
+}
+
+static void ADDSTORE() { // op: 36
+ dword a = ppop();
+ sd(a, gd(a)+ppop());
+}
+
+static void WRITE() { // op: 37
+ sd(here(), ppop());
+ allot(4);
+}
+
+static void ADD() { // op: 38
+ dword n = ppop();
+ ppush(ppop()+n);
+}
+
+static void SUB() { // op: 39
+ dword n = ppop();
+ ppush(ppop()-n);
+}
+
+static void MUL() { // op: 3a
+ dword n = ppop();
+ ppush(ppop()*n);
+}
+
+// ( a b -- r q )
+static void DIVMOD() { // op: 3b
+ dword a = ppop();
+ dword b = ppop();
+ ppush(a%b);
+ ppush(a/b);
+}
+
+static void AND() { // op: 3c
+ dword n = ppop();
+ ppush(ppop()&n);
+}
+
+static void OR() { // op: 3d
+ dword n = ppop();
+ ppush(ppop()|n);
+}
+
+static void XOR() { // op: 3e
+ dword n = ppop();
+ ppush(ppop()^n);
+}
+
+static void BOOL() { // op: 3f
+ if (ppop()) { ppush(1); } else { ppush(0); }
+}
+
+static void NOT() { // op: 40
+ if (ppop()) { ppush(0); } else { ppush(1); }
+}
+
+static void LT() { // op: 41
+ dword n = ppop();
+ if (ppop()<n) { ppush(1); } else { ppush(0); }
+}
+
+static void SHLC() { // op: 42
+ dword n = ppop();
+ ppush(n<<1);
+ ppush(n>>31);
+}
+
+static void SHRC() { // op: 43
+ dword n = ppop();
+ ppush(n>>1);
+ ppush(n&1);
+}
+
+// ( n u -- n )
+static void LSHIFT() { // op: 44
+ dword u = ppop();
+ dword n = ppop();
+ ppush(n<<u);
+}
+
+static void RSHIFT() { // op: 45
+ dword u = ppop();
+ dword n = ppop();
+ ppush(n>>u);
+}
+
+static void LITN() { // op: 46
+ ppush(0x03); // LIT
+ CWRITE();
+ WRITE();
+}
+
+static void EXECUTEWR() { // op: 47
+ ppush(0x01); // CALL
+ CWRITE();
+ WRITE();
+}
+
+static void EXITWR() { // op: 48
+ ppush(0x02); // RET
+ CWRITE();
+}
+
+static void MOVE() { // op: 49
+ dword u = ppop();
+ dword dst = ppop();
+ dword src = ppop();
+ memcpy(&vm.mem[dst], &vm.mem[src], u);
+}
+
+static void MOVEWR() { // op: 4a
+ dword u = ppop();
+ ppush(here());
+ allot(u);
+ ppush(u);
+ MOVE();
+}
+
+static void RTYPE() { // op: 4b
+ dword u = ppop();
+ dword a = ppop();
+ write(STDOUT_FILENO, &vm.mem[a], u);
+}
+
+static void WNF() { // op: 4c
+ write(STDOUT_FILENO, &vm.mem[CURWORD+1], vm.mem[CURWORD]);
+ printf(" word not found");
+ ABORT();
+}
+
+static void STACKCHK() { // op: 4d
+ if (vm.PSP > PSTOP) {
+ printf("stack underflow");
+ ABORT();
+ }
+}
+
+#define OPCNT 0x4e
+static void (*ops[OPCNT])() = {
+ BR, CALL, RET, LIT, BYE, BYEFAIL, QUIT, ABORT,
+ EXECUTE, CELL, VAL, ALIAS, DOES, SLIT, CBR, NEXT,
+ BOOTRD, EMIT, STDERR, KEY, DROP, DUP, CDUP,
+ SWAP, OVER, ROT, ROTR, NIP, TUCK, RS2PS, PS2RS, RSGET,
+ RDROP, SCNT, RCNT, ASET, AGET, ACFETCH, ACSTORE, AINC,
+ ADEC, A2RS, RS2A, TOSET, TOGET, INC, DEC, CFETCH,
+ CSTORE, CWRITE, WFETCH, WSTORE, FETCH, STORE, ADDSTORE, WRITE,
+ ADD, SUB, MUL, DIVMOD, AND, OR, XOR, BOOL,
+ NOT, LT, SHLC, SHRC, LSHIFT, RSHIFT, LITN, EXECUTEWR,
+ EXITWR, MOVE, MOVEWR, RTYPE, WNF, STACKCHK};
+
+// Dictionary building
+static void litwr(dword n) {
+ ppush(n);
+ LITN();
+}
+
+static void opwr(byte opcode) {
+ ppush(opcode);
+ CWRITE();
+}
+
+static void retwr() { opwr(0x02); }
+
+static void callwr(dword a) {
+ opwr(0x01);
+ ppush(a);
+ WRITE();
+}
+
+static void entry(char *name) {
+ dword len = strlen(name);
+ memcpy(&vm.mem[here()], name, len);
+ allot(len);
+ ppush(current());
+ WRITE();
+ ppush(len);
+ CWRITE();
+ sd(CURRENT, here());
+}
+
+static void buildsysdict() {
+ sd(HERE, 0);
+ sd(CURRENT, 0);
+ sd(COMPILING, 0);
+ opwr(0x00); // BR
+ allot(4); // forward jump
+ entry("bye"); opwr(0x04);
+ entry("byefail"); opwr(0x05);
+ entry("noop"); retwr();
+ entry("quit"); opwr(0x06);
+ entry("abort"); opwr(0x07);
+ entry("(emit)"); opwr(0x12); retwr();
+ lblmain = here();
+ sd(0x01, lblmain);
+ litwr('X');
+ callwr(find("(emit)"));
+ opwr(0x04); // BYE
+}
+
+// Interpret loop
+int main() {
+ byte opcode;
+ vm.PC = 0;
+ vm.PSP = PSTOP;
+ vm.RSP = RSTOP;
+ buildsysdict();
+ /*for (int i=0; i<here(); i++) {
+ printf("%02x ", vm.mem[i]);
+ }
+ putc('\n', stdout); */
+ while (vm.PC < MEMSZ) {
+ opcode = vm.mem[vm.PC++];
+ if (opcode >= OPCNT) {
+ printf("Illegal opcode %02x at PC %08x\n", opcode, vm.PC-1);
+ BYEFAIL();
+ } else {
+ ops[opcode]();
+ }
+ }
+ return MEMSZ - vm.PC;
+}