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:
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