duskos

dusk os fork
git clone git://git.alexwennerberg.com/duskos
Log | Files | Refs | README | LICENSE

commit 1bf8676f8ce01243bbc49368f1918f46c8aaa56a
parent 77de26ef8046f92325397612716d79db4c587967
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Thu, 22 Sep 2022 09:51:06 -0400

app/cos: add prototype cvm.c

Diffstat:
Afs/app/cos/cvm.c | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfs/doc/cc.txt | 11+++++++++--
2 files changed, 303 insertions(+), 2 deletions(-)

diff --git a/fs/app/cos/cvm.c b/fs/app/cos/cvm.c @@ -0,0 +1,294 @@ +// This doesn't compile. It's a prototype of what it will look like. +// TODO: struct +// TODO: function pointer arrays (iord iowr). +// TODO: make "extern" default. explicit "static" is better I think... +// TODO: += -= &= |= % +// TODO: %s %x (4b hex) %w (2b hex) %b (1b hex) formatting +// TODO: NULL +// TODO: typecasting studs (we don't have a checker yet, but support the syntax) +// TODO: memset() +// TODO: fseek() fopen() fclose() + +#[ + $10000 const MEMSIZE + $fffa const SP_ADDR + $ff00 const RS_ADDR + $fe00 const SYSVARS + \ Port for block reads. Each read or write has to be done in 5 IO writes: + \ 1 - r/w. 1 for read, 2 for write. + \ 2 - blkid MSB + \ 3 - blkid LSB + \ 4 - dest addr MSB + \ 5 - dest addr LSB + $03 BLK_PORT + 4 const BLKOP_CMD_SZ +]# + +struct COSVM { + unsigned char mem[ #[ MEMSIZE c]# ]; + unsigned short SP; /* parameter Stack Pointer */ + unsigned short RS; /* Return Stack pointer */ + unsigned short IP; /* Interpreter Pointer */ + unsigned short PC; /* Program Counter for HAL bytecode interpreter */ +/* Array of 0x100 function pointers to IO read and write routines. Leave to + * NULL when IO port is unhandled. */ + unsigned char (*iord[0x100])(); + void (*iowr[0x100])(unsigned char); +/* Used for keeping track of max RS and min SP during the lifetime of the + * program. Useful for debugging. */ + unsigned short maxRS; + unsigned short minSP; + int running; +} +static struct COSVM vm; +static struct File *blkfp; +/* Stores blkop command. Bytes flow from left (byte 0) to right (byte 3) + * We know we have a full command when last byte is nonzero. After + * processing the cmd, we reset blkop to 0. */ +static unsigned char blkop[ #[ BLKOP_CMD_SZ c]# ]; + +/* Read single byte from I/O handler, if set. addr is a word only because of */ +/* Forth's cell size, but can't actually address more than a byte-full of ports. */ +static unsigned char io_read(unsigned short addr) +{ + addr &= 0xff; + unsigned char(*fn)() = vm.iord[addr]; + if (fn != NULL) { + return fn(); + } else { + fprintf(addr, "Out of bounds I/O read: %d\n", ConsoleOut()); + return 0; + } +} + +static void io_write(unsigned short addr, unsigned char val) +{ + addr &= 0xff; + void (*fn)(unsigned char) = vm.iowr[addr]; + if (fn != NULL) { + fn(val); + } else { + fprintf( + addr, val, val, "Out of bounds I/O write: %d / %d (0x%x)\n", + ConsoleOut()); + } +} + +/* I/O hook to read/write a chunk of 1024 byte to blkfs at specified blkid. */ +/* This is used by EFS@ and EFS! in xcomp.fs. */ +/* See comment above BLK_PORT define for poking convention. */ +static void iowr_blk(unsigned char val) +{ + unsigned char rw = blkop[3]; + if (rw) { + unsigned short blkid = + (unsigned short)blkop[2] << 8 | (unsigned short)blkop[1]; + unsigned short dest = + (unsigned short)blkop[0] << 8 | (unsigned short)val; + memset(blkop, 0, #[ BLKOP_CMD_SZ c]# ); + fseek(blkid*1024, blkfp); + if (rw==2) { /* write */ + fwrite(&vm.mem[dest], 1024, 1, blkfp); + } else { /* read */ + fread(&vm.mem[dest], 1024, 1, blkfp); + } + } else { + move(blkop, blkop+1, #[ BLKOP_CMD_SZ 1- c]# ); + blkop[0] = val; + } +} + +/* get/set word from/to memory */ +static unsigned short gw(unsigned short addr) { + return vm.mem[addr+1] << 8 | vm.mem[addr]; } +static void sw(unsigned short addr, unsigned short val) { + vm.mem[addr] = val; + vm.mem[addr+1] = val >> 8; +} +static unsigned short peek() { return gw(vm.SP); } +/* pop word from SP */ +static unsigned short pop() { unsigned short n = peek(); vm.SP+=2; return n; } +unsigned short VM_PS_pop() { return pop(); } + +/* push word to SP */ +static void push(unsigned short x) { + vm.SP -= 2; + sw(vm.SP, x); + if (vm.SP < vm.minSP) { vm.minSP = vm.SP; } +} +void VM_PS_push(unsigned short n) { push(n); } +/* pop word from RS */ +static unsigned short popRS() { + unsigned short x = gw(vm.RS); vm.RS -= 2; return x; +} +/* push word to RS */ +static void pushRS(unsigned short val) { + vm.RS += 2; + sw(vm.RS, val); + if (vm.RS > vm.maxRS) { vm.maxRS = vm.RS; } +} + +static unsigned short pc16() { + unsigned short n = gw(vm.PC); vm.PC+=2; return n; } +static unsigned short pc8() { + unsigned char b = vm.mem[vm.PC]; vm.PC++; return b; } + +/* Native words */ +static void lblnext() { vm.PC = gw(vm.IP); vm.IP += 2; } +static void lblxt() { pushRS(vm.IP); vm.IP = pop(); lblnext(); } +static void lbldoes() { vm.PC = pop(); push(vm.PC+2); vm.PC = gw(vm.PC); } +static void lblval() { + unsigned short a; + if (vm.mem[ #[ SYSVARS $18 + c]# ]) { // TO? + vm.mem[ #[ SYSVARS $18 + c]# ] = 0; + a = pop(); + sw(a, pop()); + } else { + push(gw(pop())); + } + lblnext(); +} +static void DUP() { push(peek()); } +static void DROP() { pop(); } +static void SWAP() { + unsigned short a = pop(); unsigned short b = pop(); push(a); push(b); } +static void OVER() { + unsigned short a = pop(); unsigned short b = peek(); push(a); push(b); } +static void ROT() { + unsigned short c = pop(); + unsigned short b = pop(); + unsigned short a = pop(); + push(b); push(c); push(a); } +static void RS2PS() { push(popRS()); } +static void PS2RS() { pushRS(pop()); } +static void RFETCH() { push(gw(vm.RS)); } +static void RDROP() { popRS(); } +static void PUSHi() { push(pc16()); } +static void PUSHii() { push(gw(pc16())); } +static void CFETCH() { push(vm.mem[pop()]); } +static void FETCH() { push(gw(pop())); } +static void CSTORE() { unsigned short a = pop(); vm.mem[a] = pop(); } +static void STORE() { unsigned short a = pop(); sw(a, pop()); } +static void EXECUTE() { vm.PC = pop(); } +static void JMPi() { vm.PC = gw(vm.PC); } +static void JMPii() { vm.PC = gw(gw(vm.PC)); } +static void CALLi() { push(vm.PC+2); JMPi(); } +static void NOT() { push(!pop()); } +static void AND() { push(pop() & pop()); } +static void OR() { push(pop() | pop()); } +static void XOR() { push(pop() ^ pop()); } +static void PLUS() { + unsigned short b = pop(); unsigned short a = pop(); push(a+b); } +static void SUB() { + unsigned short b = pop(); unsigned short a = pop(); push(a-b); } +static void BR() { + unsigned short off = vm.mem[vm.IP]; + if (off > 0x7f) off |= 0xff00; vm.IP += off; } +static void CBR() { if (pop()) { vm.IP++; } else { BR(); } } +static void NEXT() { + unsigned short n = popRS()-1; + if (n) { pushRS(n); BR(); } + else { vm.IP++; } +} +static void PCSTORE() { + unsigned short a = pop(); unsigned short val = pop(); + io_write(a, val); +} +static void PCFETCH() { push(io_read(pop())); } +static void MULT() { + unsigned short b = pop(); unsigned short a = pop(); push(a * b); } +static void DIVMOD() { + unsigned short b = pop(); unsigned short a = pop(); + push(a % b); push(a / b); +} +static void QUIT() { vm.RS = #[ RS_ADDR c]# ; } +static void ABORT() { vm.SP = #[ SP_ADDR c]# ; } +static void RCNT() { push((vm.RS - #[ RS_ADDR c]# ) / 2); } +static void SCNT() { push((#[ SP_ADDR c]# - vm.SP) / 2); } +static void BYE() { vm.running = 0; } +static void EXIT() { vm.IP = popRS(); } +static void CDUP() { unsigned short a = peek(); if (a) push(a); } +static void LIT8() { push(vm.mem[vm.IP++]); } +static void LIT16() { push(gw(vm.IP)); vm.IP+=2; } +static void LT() { + unsigned short b = pop(); unsigned short a = pop(); push(a<b); } + +#[ 67 const OPCNT ]# +static void (*ops[ #[ OPCNT c]# ])() = { + DUP, DROP, PUSHi, PUSHii, SWAP, OVER, ROT, lblnext, CBR, NEXT, + CALLi, JMPi, lblxt, EXIT, CDUP, LIT8, LIT16, JMPii, lbldoes, lblval, + NULL, EXECUTE, NULL, NULL, NULL, NULL, RDROP, NULL, PLUS, SUB, BR, + NULL, NULL, LT, NULL, NULL, NULL, NULL, NOT, AND, OR, XOR, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, PCSTORE, PCFETCH, MULT, DIVMOD, QUIT, ABORT, RCNT, SCNT, BYE, + RFETCH, RS2PS, PS2RS, CFETCH, FETCH, STORE, CSTORE +}; + +static void opexec(unsigned char op) { + if (op < #[ OPCNT c]# ) { + ops[op](); + } else { + fprintf(op, vm.PC, "Out of bounds op %w. PC: %w\n", ConsoleOut()); + vm.running = 0; + } +} + +struct COSVM* COSVM_init(char *bin_path, char *blkfs_path) +{ + struct File *bfp = fopen(bin_path); + if (!bfp) { + fprintf("Can't open forth bin\n", ConsoleOut()); + return NULL; + } + int c = fgetc(bfp); + while (c != EOF) { + vm.mem[i++] = c; + c = fgetc(bfp); + } + fclose(bfp); + fprintf(blkfs_path, "Using blkfs %s\n", ConsoleOut()); + blkfp = fopen(blkfs_path); + if (!blkfp) { + fprintf("Can't open\n", ConsoleOut()); + return NULL; + } + if (blkfs->size < 100 * 1024) { + fclose(blkfp); + fprintf("blkfs too small, something's wrong, aborting.\n", ConsoleOut()); + return NULL; + } + memset(blkop, 0, #[ BLKOP_CMD_SZ c]# ); + vm.SP = #[ SP_ADDR c]# ; + vm.RS = #[ RS_ADDR c]# ; + vm.minSP = #[ SP_ADDR c]# ; + vm.maxRS = #[ RS_ADDR c]# ; + memset(vm.iord, 0, $400); + memset(vm.iowr, 0, $400); + vm.iowr[ #[ BLK_PORT c]# ] = iowr_blk; + vm.PC = 0; + vm.running = 1; + return &vm; +} + +void COSVM_deinit() +{ + fclose(blkfp); +} + +int COSVM_steps(int n) { + if (!vm.running) { + fprintf("machine halted!\n", ConsoleOut()); + return 0; + } + while (n && vm.running) { + opexec(vm.mem[vm.PC++]); + n--; + } + return vm.running; +} + +void COSVM_printdbg() { + fprintf( + vm.SP, vm.minSP, vm.RS, vm.maxRS + "SP %w (%w) RS %w (%w)", ConsoleOut()); +} diff --git a/fs/doc/cc.txt b/fs/doc/cc.txt @@ -43,6 +43,9 @@ interpretation when it parses the last closing "}" character. Example: ## Differences in the core language +* no C preprocessor, the preprocessor is Forth itself, through macros. No + #include either. Structs and constants, in Dusk OS, stay in memory, no need + for header files. * no 64bit types * no long, redundant with int * no double, float is always 32b @@ -57,8 +60,12 @@ interpretation when it parses the last closing "}" character. Example: * string literals are not null-terminated, but "counted strings". The exact same format as system strings. * Added pspop() and pspush() built-in functions. -* By default, functions have internal (static) linkage. The "extern" keyword - gives them external linkage (an entry in the system dict). +* No typedefs. It seems limiting (and verbose) at first, but I think it makes + the code clearer in the end. Because we have fixed type length, no length- + based aliasing is necessary, so typedef becomes a mere convenience. Let's try + to skip it. +* { } brackets always mandatory after loop and condition statements. Just wrap + your one liners around { }. ## Caller save