#include #include #include #include #include "vm.h" // 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 #define BLK_PORT 0x03 static VM vm; static uint64_t blkop = 0; // 5 bytes static FILE *blkfp; // 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-ful of ports. static byte io_read(word addr) { addr &= 0xff; IORD fn = vm.iord[addr]; if (fn != NULL) { return fn(); } else { fprintf(stderr, "Out of bounds I/O read: %d\n", addr); return 0; } } static void io_write(word addr, byte val) { addr &= 0xff; IOWR fn = vm.iowr[addr]; if (fn != NULL) { fn(val); } else { fprintf(stderr, "Out of bounds I/O write: %d / %d (0x%x)\n", addr, val, val); } } // 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(byte val) { blkop <<= 8; blkop |= val; byte rw = blkop >> 32; if (rw) { word blkid = (blkop >> 16); word dest = blkop & 0xffff; blkop = 0; fseek(blkfp, blkid*1024, SEEK_SET); if (rw==2) { // write fwrite(&vm.mem[dest], 1024, 1, blkfp); } else { // read fread(&vm.mem[dest], 1024, 1, blkfp); } } } // get/set word from/to memory static word gw(word addr) { return vm.mem[addr+(word)1] << 8 | vm.mem[addr]; } static void sw(word addr, word val) { vm.mem[addr] = val; vm.mem[addr+(word)1] = val >> 8; } // pop word from SP static word pop() { if (vm.uflw) return 0; if (vm.SP >= SP_ADDR) { vm.uflw = true; return 0; } return vm.mem[vm.SP++] | vm.mem[vm.SP++] << 8; } // push word to SP static void push(word x) { vm.SP -= 2; if (vm.SP <= vm.RS) { vm.oflw = true; vm.SP = SP_ADDR; vm.RS = RS_ADDR; return; } sw(vm.SP, x); if (vm.SP < vm.minSP) { vm.minSP = vm.SP; } } // pop word from RS static word popRS() { if (vm.uflw) return 0; if (vm.RS <= RS_ADDR) { vm.uflw = true; return 0; } word x = gw(vm.RS); vm.RS -= 2; return x; } // push word to RS static void pushRS(word val) { vm.RS += 2; if (vm.SP <= vm.RS) { vm.oflw = true; vm.SP = SP_ADDR; vm.RS = RS_ADDR; return; } sw(vm.RS, val); if (vm.RS > vm.maxRS) { vm.maxRS = vm.RS; } } // The functions below directly map to native forth words defined in the // dictionary (doc/dict.txt) static void execute(word wordref) { byte wtype = vm.mem[wordref]; switch (wtype) { case 0: // native vm.nativew[vm.mem[wordref+(word)1]](); break; case 1: // compiled pushRS(vm.IP); vm.IP = wordref+1; break; case 2: // cell push(wordref+1); break; case 3: // does push(wordref+1); pushRS(vm.IP); vm.IP = gw(wordref+3); break; case 4: // alias execute(gw(wordref+1)); break; case 5: // switch execute(gw(gw(wordref+1))); break; } } static word find(word daddr, word waddr) { byte len = vm.mem[waddr]; waddr++; while (1) { if ((vm.mem[daddr-(word)1] & 0x7f) == len) { word d = daddr-3-len; // Sanity check if ((waddr+len >= MEMSIZE) || (d+len) >= MEMSIZE) return 0; if (strncmp(&vm.mem[waddr], &vm.mem[d], len) == 0) { return daddr; } } daddr -= 3; word offset = gw(daddr); if (offset) { daddr -= offset; } else { return 0; } } } static void EXIT() { vm.IP = popRS(); } static void _br_() { word off = vm.mem[vm.IP]; if (off > 0x7f ) { off -= 0x100; } vm.IP += off; } static void _cbr_() { if (!pop()) { _br_(); } else { vm.IP++; } } static void _loop_() { word I = gw(vm.RS); I++; sw(vm.RS, I); if (I == gw(vm.RS-2)) { // don't branch popRS(); popRS(); vm.IP++; } else { // branch _br_(); } } static void SP_to_R_2() { word x = pop(); pushRS(pop()); pushRS(x); } static void nlit() { push(gw(vm.IP)); vm.IP += 2; } static void slit() { push(vm.IP); vm.IP += vm.mem[vm.IP] + 1; } static void SP_to_R() { pushRS(pop()); } static void R_to_SP() { push(popRS()); } static void R_to_SP_2() { word x = popRS(); push(popRS()); push(x); } static void EXECUTE() { execute(pop()); } static void ROT() { // a b c -- b c a word c = pop(); word b = pop(); word a = pop(); push(b); push(c); push(a); } static void ROTR() { // a b c -- c a b word c = pop(); word b = pop(); word a = pop(); push(c); push(a); push(b); } static void DUP() { // a -- a a word a = pop(); push(a); push(a); } static void CDUP() { word a = pop(); push(a); if (a) { push(a); } } static void DROP() { pop(); } static void SWAP() { // a b -- b a word b = pop(); word a = pop(); push(b); push(a); } static void OVER() { // a b -- a b a word b = pop(); word a = pop(); push(a); push(b); push(a); } static void PICK() { word x = pop(); push(gw(vm.SP+x*2)); } static void _roll_() { // "1 2 3 4 4 (roll)" --> "1 3 4 4" word x = pop(); while (x) { vm.mem[vm.SP+x+(word)2] = vm.mem[vm.SP+x]; x--; } } static void DROP2() { pop(); pop(); } static void DUP2() { // a b -- a b a b word b = pop(); word a = pop(); push(a); push(b); push(a); push(b); } static void S0() { push(SP_ADDR); } static void Saddr() { push(vm.SP); } static void AND() { push(pop() & pop()); } static void OR() { push(pop() | pop()); } static void XOR() { push(pop() ^ pop()); } static void NOT() { push(!pop()); } static void PLUS() { push(pop() + pop()); } static void MINUS() { word b = pop(); word a = pop(); push(a - b); } static void MULT() { push(pop() * pop()); } static void DIVMOD() { word b = pop(); word a = pop(); push(a % b); push(a / b); } static void STORE() { word a = pop(); word val = pop(); sw(a, val); } static void FETCH() { push(gw(pop())); } static void CSTORE() { word a = pop(); word val = pop(); vm.mem[a] = val; } static void CFETCH() { push(vm.mem[pop()]); } static void IO_OUT() { word a = pop(); word val = pop(); io_write(a, val); } static void IO_IN() { push(io_read(pop())); } static void RI() { push(gw(vm.RS)); } static void RI_() { push(gw(vm.RS-2)); } static void RJ() { push(gw(vm.RS-4)); } static void BYE() { vm.running = false; } static void _resSP_() { vm.SP = SP_ADDR; } static void _resRS_() { vm.RS = RS_ADDR; } static void Seq() { word s1 = pop(); word s2 = pop(); byte len = vm.mem[s1]; if (len == vm.mem[s2]) { s1++; s2++; push(strncmp(&vm.mem[s1], &vm.mem[s2], len) == 0); } else { push(0); } } static void CMP() { word b = pop(); word a = pop(); if (a == b) { push(0); } else if (a > b) { push(1); } else { push(-1); } } static void _find() { word waddr = pop(); word daddr = pop(); daddr = find(daddr, waddr); if (daddr) { push(daddr); push(1); } else { push(waddr); push(0); } } static void ZERO() { push(0); } static void ONE() { push(1); } static void MONE() { push(-1); } static void PLUS1() { push(pop()+1); } static void MINUS1() { push(pop()-1); } static void MINUS2() { push(pop()-2); } static void PLUS2() { push(pop()+2); } static void RSHIFT() { word u = pop(); push(pop()>>u); } static void LSHIFT() { word u = pop(); push(pop()<>8); push(n&0xff); } static void SPLITM() { word n = pop(); push(n&0xff); push(n>>8); } static void native(NativeWord func) { vm.nativew[vm.nativew_count++] = func; } VM* VM_init(char *bin_path, char *blkfs_path) { fprintf(stderr, "Using blkfs %s\n", blkfs_path); blkfp = fopen(blkfs_path, "r+"); if (!blkfp) { fprintf(stderr, "Can't open\n"); return NULL; } fseek(blkfp, 0, SEEK_END); if (ftell(blkfp) < 100 * 1024) { fclose(blkfp); fprintf(stderr, "emul/blkfs too small, something's wrong, aborting.\n"); return NULL; } fseek(blkfp, 0, SEEK_SET); FILE *bfp = fopen(bin_path, "r"); if (!bfp) { fprintf(stderr, "Can't open forth.bin\n"); return NULL; } int i = 0; int c = getc(bfp); while (c != EOF) { vm.mem[i++] = c; c = getc(bfp); } fclose(bfp); // initialize rest of memory with random data. Many, many bugs we've seen in // Collapse OS were caused by bad initialization and weren't reproducable // in CVM because it has a neat zeroed-out memory. Let's make bugs easier // to spot. while (i<0x10000) { vm.mem[i++] = random(); } vm.SP = SP_ADDR; vm.RS = RS_ADDR; vm.minSP = SP_ADDR; vm.maxRS = RS_ADDR; vm.nativew_count = 0; for (int i=0; i<0x100; i++) { vm.iord[i] = NULL; vm.iowr[i] = NULL; } vm.iowr[BLK_PORT] = iowr_blk; // Added in the same order as in xcomp.fs native(EXIT); native(_br_); native(_cbr_); native(_loop_); native(nlit); native(slit); native(SP_to_R); native(R_to_SP); native(SP_to_R_2); native(R_to_SP_2); native(EXECUTE); native(ROT); native(DUP); native(CDUP); native(DROP); native(SWAP); native(OVER); native(PICK); native(_roll_); native(DROP2); native(DUP2); native(S0); native(Saddr); native(AND); native(OR); native(XOR); native(NOT); native(PLUS); native(MINUS); native(MULT); native(DIVMOD); native(STORE); native(FETCH); native(CSTORE); native(CFETCH); native(IO_OUT); native(IO_IN); native(RI); native(RI_); native(RJ); native(BYE); native(_resSP_); native(_resRS_); native(Seq); native(CMP); native(_find); native(ZERO); native(ONE); native(MONE); native(PLUS1); native(MINUS1); native(PLUS2); native(MINUS2); native(RSHIFT); native(LSHIFT); native(TICKS); native(ROTR); native(SPLITL); native(SPLITM); vm.IP = gw(0x04) + 1; // BOOT sw(SYSVARS+0x02, gw(0x08)); // CURRENT sw(SYSVARS+0x04, gw(0x08)); // HERE vm.uflw = false; vm.oflw = false; vm.running = true; return &vm; } void VM_deinit() { fclose(blkfp); } bool VM_steps(int n) { if (!vm.running) { fprintf(stderr, "machine halted!\n"); return false; } while (n && vm.running) { word wordref = gw(vm.IP); vm.IP += 2; execute(wordref); if (vm.uflw) { vm.uflw = false; execute(gw(0x06)); /* uflw */ } if (vm.oflw) { vm.oflw = false; execute(gw(0x13)); /* oflw */ } n--; } return vm.running; } void VM_memdump() { fprintf(stderr, "Dumping memory to memdump. IP %04x\n", vm.IP); FILE *fp = fopen("memdump", "w"); fwrite(vm.mem, 0x10000, 1, fp); fclose(fp); } void VM_debugstr(char *s) { sprintf(s, "SP %04x (%04x) RS %04x (%04x)", vm.SP, vm.minSP, vm.RS, vm.maxRS); } void VM_printdbg() { char buf[0x100]; VM_debugstr(buf); fprintf(stderr, "%s\n", buf); }