mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-17 09:48:05 +11:00
adea75e50a
I'm pretty happy about how lightweight the implementation turns out to be.
443 lines
11 KiB
C
443 lines
11 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#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
|
|
|
|
#ifndef FBIN_PATH
|
|
#error FBIN_PATH needed
|
|
#endif
|
|
|
|
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+1] << 8 | vm.mem[addr]; }
|
|
static void sw(word addr, word val) {
|
|
vm.mem[addr] = val;
|
|
vm.mem[addr+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+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];
|
|
while (1) {
|
|
if ((vm.mem[daddr-1] & 0x7f) == len) {
|
|
if (strncmp(&vm.mem[waddr+1], &vm.mem[daddr-3-len], 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 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+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()<<u); }
|
|
static void TICKS() { usleep(pop()); }
|
|
|
|
static void native(NativeWord func) {
|
|
vm.nativew[vm.nativew_count++] = func;
|
|
}
|
|
|
|
VM* VM_init(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(FBIN_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);
|
|
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);
|
|
}
|