equi

A self-descriptive stack-based PC platform
git clone git://git.luxferre.top/equi.git
Log | Files | Refs | README | LICENSE

commit b16e692e898d416f27880d4900b57690435182c2
parent 79c00012935cdcaa58b55624137c195c897466f2
Author: Luxferre <lux@ferre>
Date:   Sat, 13 Aug 2022 18:48:31 +0300

Yet another RAM structure revamp for memory protection and future multitasking - to be reflected in README

Diffstat:
Mequi.c | 249+++++++++++++++++++++++++++++++++++++++++++------------------------------------
1 file changed, 137 insertions(+), 112 deletions(-)

diff --git a/equi.c b/equi.c @@ -29,7 +29,6 @@ #ifdef __CC65__ #include <conio.h> #else - #define cgetc() (getchar()) #define cputc(c) (putchar(c)) #endif @@ -61,9 +60,9 @@ #define LIT_STACK_SIZE 32u #endif -/* Command buffer size in bytes */ +/* GPD area size in bytes */ #ifndef GPD_AREA_SIZE -#define GPD_AREA_SIZE 5000u +#define GPD_AREA_SIZE 4096u #endif /* Command buffer size in bytes */ @@ -76,6 +75,11 @@ #define CLT_ENTRIES_MAX 512u #endif +/* Maximum amount of task table entries */ +#ifndef EQUI_TASKS_MAX +#define EQUI_TASKS_MAX 1 +#endif + /* Persistent storage sandbox file name */ #ifndef PERSIST_FILE #define PERSIST_FILE "PERS.DAT" @@ -84,38 +88,44 @@ /* Some necessary constants and offsets derived from the above values */ #define STACK_SIZE_WORDS (STACK_SIZE / WS) /* Main and return stack size in words */ -/* - * Structures that describe Equi machine RAM using the above configuration - */ +/* Structures that describe Equi machine RAM using the above configuration */ -struct CLTEntry { +struct CLTEntry { /* one entry in the compilation lookup table */ ushort nhash; /* compiled word name hash */ ushort loc; /* compiled word location */ }; -struct EquiRAM { - ushort stack_size; /* main/return stack size in words */ - uchar literal_stack_size; /* literal stack size in bytes */ - uchar lsp; /* literal stack pointer */ +struct EquiCtx { /* one Equi program context */ + ushort id; /* task ID */ ushort msp; /* main stack pointer */ ushort rsp; /* return stack pointer */ - ushort clt_start; /* compilation lookup table start */ - ushort gpd_start; /* GPD area start */ - ushort cmd_start; /* command buffer start */ - ushort cmd_size; /* command buffer size in bytes */ + ushort lsp; /* literal stack pointer */ + ushort cltp; /* compilation lookup table pointer */ + ushort gpd_start; /* GPD area start for this task */ + ushort cmd_start; /* command buffer start for this task */ + ushort cmd_size; /* size of loaded code in bytes for this task */ ushort pc; /* program counter */ + ushort main_stack[STACK_SIZE_WORDS]; + ushort return_stack[STACK_SIZE_WORDS]; + uchar literal_stack[LIT_STACK_SIZE]; + struct CLTEntry clt[CLT_ENTRIES_MAX]; /* compilation lookup table */ + uchar gpd[GPD_AREA_SIZE]; +}; + +struct EquiRAM { + ushort stack_size; /* main/return stack size in words */ + uchar literal_stack_size; /* literal stack size in bytes */ + ushort cmd_start; /* (global) command buffer start */ + ushort cmd_size; /* (global) command buffer size in bytes */ ushort cbp; /* compilation buffer pointer */ - ushort cltp; /* compilation lookup table pointer */ ushort ibp; /* input buffer pointer */ uchar II; /* instruction ignore mode flag */ uchar CM; /* compilation mode flag */ uchar IM; /* interpretation mode flag */ uchar MM; /* minification bypass mode flag */ - ushort main_stack[STACK_SIZE_WORDS]; - ushort return_stack[STACK_SIZE_WORDS]; - uchar literal_stack[LIT_STACK_SIZE]; - struct CLTEntry clt[CLT_ENTRIES_MAX]; /* compilation lookup table */ - uchar gpd[GPD_AREA_SIZE]; + ushort taskcount; /* count of active tasks */ + ushort taskid; /* currently running task ID - necessary? */ + struct EquiCtx tasks[EQUI_TASKS_MAX]; uchar cmdbuf[CMD_BUF_SIZE]; }; @@ -125,8 +135,8 @@ static struct EquiRAM ram; /* Also create an alternative view of the same RAM area for direct offset-based access */ static uchar* flatram = (uchar *)&ram; -/* write protection threshold RAM address */ -static ushort write_protection_threshold = 0; +/* reference to the current task context */ +static struct EquiCtx *curtask; /* Error reporting codes */ enum EquiErrors { @@ -140,13 +150,14 @@ enum EquiErrors { INVALID_WORD, PORT_IO_ERROR, PERSIST_IO_ERROR, - RESTRICTED_WRITE_ERROR + RESTRICTED_WRITE_ERROR, + OUT_OF_BOUNDS_JUMP }; /* Error reporting method */ void trapout(errcode) { if(errcode > 0) { - fprintf(stderr, "\nError %d at 0x%x (instruction %c): ", errcode, ram.pc, ram.cmdbuf[ram.pc]); + fprintf(stderr, "\nError %d at 0x%x (task 0x%x, instruction %c): ", errcode, curtask->pc, curtask->id, flatram[curtask->pc]); switch(errcode) { case STACK_OVERFLOW: cerr("Stack overflow\n"); @@ -178,13 +189,15 @@ void trapout(errcode) { case RESTRICTED_WRITE_ERROR: cerr("Attempt to write to a restricted RAM area\n"); break; + case OUT_OF_BOUNDS_JUMP: + cerr("Attempt to jump outside the task context\n"); + break; } exit(errcode); } } /* Equi instructions enum */ - enum EquiInstructions { INS_IISTART='(', INS_IIEND=')', @@ -232,7 +245,6 @@ enum EquiInstructions { }; /* Equi known ports enum */ - enum EquiPorts { PORT_ECHO = 0x0, /* echo port, used for testing: R1=P1, R2=P2 */ PORT_RANDOM, /* RNG port: P1 = valueFrom, P2 = valueTo => R1=rand(from, to), R2 = rand(from, to) */ @@ -241,53 +253,53 @@ enum EquiPorts { /* push a value onto the main stack */ void pushMain(ushort val) { - ram.main_stack[ram.msp++] = val; - if(ram.msp >= STACK_SIZE_WORDS) + curtask->main_stack[curtask->msp++] = val; + if(curtask->msp >= STACK_SIZE_WORDS) trapout(STACK_OVERFLOW); } /* push a value onto the return stack */ void pushRet(ushort val) { - ram.return_stack[ram.rsp++] = val; - if(ram.rsp >= STACK_SIZE_WORDS) + curtask->return_stack[curtask->rsp++] = val; + if(curtask->rsp >= STACK_SIZE_WORDS) trapout(STACK_OVERFLOW); } /* pop a value from the main stack */ ushort popMain() { - if(ram.msp == 0) { + if(curtask->msp == 0) { trapout(STACK_UNDERFLOW); return 0; } else - return ram.main_stack[--ram.msp]; + return curtask->main_stack[--curtask->msp]; } /* pop a value from the return stack */ ushort popRet() { - if(ram.rsp == 0) { + if(curtask->rsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else - return ram.return_stack[--ram.rsp]; + return curtask->return_stack[--curtask->rsp]; } /* push a character to the literal stack */ void pushLit(uchar c) { - ram.literal_stack[ram.lsp++] = c; - if(ram.lsp >= LIT_STACK_SIZE) + curtask->literal_stack[curtask->lsp++] = c; + if(curtask->lsp >= LIT_STACK_SIZE) trapout(STACK_OVERFLOW); } /* pop a character from the literal stack */ uchar popLit() { - if(ram.lsp == 0) { + if(curtask->lsp == 0) { trapout(STACK_UNDERFLOW); return 0; } else - return ram.literal_stack[--ram.lsp]; + return curtask->literal_stack[--curtask->lsp]; } /* convert ASCII code of a hex digit to the digit value */ @@ -298,15 +310,14 @@ uchar a2d(uchar a) { /* shape 2-byte vlaue on the main stack from up to 4 values of the literal stack */ void pushLitVal() { uchar p[4U] = {0,0,0,0}, i, thr = 4U; - if(ram.lsp < 4U) thr = ram.lsp; + if(curtask->lsp < 4U) thr = curtask->lsp; for(i=0;i<thr;++i) p[3-i] = a2d(popLit()); pushMain((p[0]<<12U) | (p[1]<<8U) | (p[2]<<4U) | p[3]); - ram.lsp = 0; /* clear the literal stack */ + curtask->lsp = 0; /* clear the literal stack */ } /* CCITT CRC16 helper */ - ushort crc16(const uchar* data_p, uchar length) { uchar x; ushort crc = 0xFFFFu; @@ -319,13 +330,26 @@ ushort crc16(const uchar* data_p, uchar length) { } /* Short endianness conversion helper */ - ushort tobig(ushort val) { return (val>>8)|((val&255)<<8); } -/* Persistent operation handler */ +/* Task-based memory address jump checker */ +uchar taskMemJumpAllowed(addr) { + /* task 0 is allowed to jump anywhere in the command buffer, others only in their own zone */ + if(curtask->id) + return (addr >= curtask->cmd_start) && (addr < curtask->cmd_start + curtask->cmd_size); + else + return addr >= ram.cmd_start; +} +/* Task-based memory address write checker */ +uchar taskMemWriteAllowed(addr) { + /* task 0 is allowed to write anywhere in the command buffer, others only in their own zone */ + return (addr >= curtask->gpd_start && addr < (curtask->gpd_start + GPD_AREA_SIZE)) || taskMemJumpAllowed(addr); +} + +/* Persistent operation handler */ ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWrite) { ushort status = 0, proc; if(pfd) { @@ -333,7 +357,7 @@ ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWr if(isWrite) /* writing to the persistent area from the memory */ proc = (ushort) fwrite(&flatram[maddr], 1, dataLen, pfd); else { /* reading from the persistent area into the memory */ - if(maddr < write_protection_threshold) + if(!taskMemWriteAllowed(maddr)) trapout(RESTRICTED_WRITE_ERROR); proc = (ushort) fread(&flatram[maddr], 1, dataLen, pfd); } @@ -345,7 +369,6 @@ ushort persistOp(FILE *pfd, ushort maddr, ushort dataLen, ushort blk, uchar isWr } /* Port I/O handler */ - void portIO(ushort port, ushort p2, ushort p1) { ushort r1 = 0, r2 = 0, status = 0; switch(port) { @@ -368,19 +391,27 @@ void portIO(ushort port, ushort p2, ushort p1) { pushMain(status); } -/* Main interpreter loop */ +/* Task loader method */ +struct EquiCtx* equi_load_task(ushort progStart, ushort len) { + struct EquiCtx *taskptr = &ram.tasks[ram.taskcount]; /* refer to the last available entry */ + taskptr->msp = taskptr->rsp = taskptr->lsp = taskptr->cltp = 0; /* init stacks and CLT */ + taskptr->pc = progStart - 1U; /* init program counter for preincrement logic */ + taskptr->cmd_start = progStart; /* actual command buffer start (from the start of vRAM) */ + taskptr->cmd_size = len; /* actual command buffer size */ + taskptr->id = ram.taskcount; /* assign task ID */ + taskptr->gpd_start = (ushort) (taskptr->gpd - flatram); /* assign GPD area start */ + ++ram.taskcount; /* increase task count */ + return taskptr; +} +/* Main interpreter loop */ void equi_main_loop() { uchar instr; ushort lhash, pbuf, pbuf2; /* try to open the persistent sandbox file */ FILE *pfd = fopen(PERSIST_FILE, "r+b"); - /* reset all stacks before running and reinit CLT */ - ram.msp = ram.rsp = ram.lsp = ram.cltp = 0; - /* reset pc */ - ram.pc = 65535U; while(1) { /* iterate over the instructions in the command buffer */ - instr = ram.cmdbuf[++ram.pc]; + instr = flatram[++curtask->pc]; /* silently exit on zero or FF */ if(instr == 0 || instr == 0xFFu) break; /* first, check for II mode */ @@ -392,15 +423,15 @@ void equi_main_loop() { /* then, check for compilation mode */ if(ram.CM) { if(instr == INS_CMEND) { /* trigger word compilation logic as per the spec */ - if(ram.lsp < 1) + if(curtask->lsp < 1) trapout(STACK_UNDERFLOW); - ram.cmdbuf[ram.pc] = INS_RET; /* in-place patch this instruction to R */ + flatram[curtask->pc] = INS_RET; /* in-place patch this instruction to R */ /* hash and save compiled word */ - ram.clt[ram.cltp].nhash = crc16(&ram.literal_stack[0], ram.lsp); - ram.lsp = 0; /* clear the literal stack */ - ram.clt[ram.cltp].loc = ram.cbp; /* stored the compiled code location from the most recent CBP value */ - ++ram.cltp; /* increase the word */ - if(ram.cltp == CLT_ENTRIES_MAX) + curtask->clt[curtask->cltp].nhash = crc16(&(curtask->literal_stack[0]), curtask->lsp); + curtask->lsp = 0; /* clear the literal stack */ + curtask->clt[curtask->cltp].loc = ram.cbp; /* stored the compiled code location from the most recent CBP value */ + ++curtask->cltp; /* increase the word */ + if(curtask->cltp == CLT_ENTRIES_MAX) trapout(CLT_OVERFLOW); ram.CM = 0; /* unset compilation mode flag */ } @@ -413,11 +444,11 @@ void equi_main_loop() { continue; } /* then trigger literal auto-push if applicable */ - if(ram.lsp > 0 && instr != INS_LITSTR && instr != INS_LITCALL && instr != INS_LITINT && instr != INS_CMSTART) + if(curtask->lsp > 0 && instr != INS_LITSTR && instr != INS_LITCALL && instr != INS_LITINT && instr != INS_CMSTART) pushLitVal(); switch(instr) { /* then perform all main interpretation logic */ case INS_CMSTART: /* compilation start */ - ram.cbp = ram.pc + 1U; /* save CBP */ + ram.cbp = curtask->pc + 1U; /* save CBP */ ram.CM = 1U; /* raise CM flag */ break; case CR: @@ -431,26 +462,26 @@ void equi_main_loop() { pushLitVal(); break; case INS_LITSTR: /* literal stack -> each char at main stack as short */ - while(ram.lsp) + while(curtask->lsp) pushMain((ushort)popLit()); - ram.lsp = 0; + curtask->lsp = 0; break; case INS_LITCALL: /* call the saved word from the literal */ - if(ram.lsp < 1) + if(curtask->lsp < 1) trapout(STACK_UNDERFLOW); - lhash = crc16(&ram.literal_stack[0], ram.lsp); + lhash = crc16(&(curtask->literal_stack[0]), curtask->lsp); for(pbuf=0;pbuf<CLT_ENTRIES_MAX;++pbuf) - if(ram.clt[pbuf].nhash == lhash) { - pushRet(ram.pc); /* first, save the PC into the return stack */ - ram.pc = ram.clt[pbuf].loc; /* then jump to the word location */ + if(curtask->clt[pbuf].nhash == lhash) { + pushRet(curtask->pc); /* first, save the PC into the return stack */ + curtask->pc = curtask->clt[pbuf].loc; /* then jump to the word location */ break; } if(pbuf >= CLT_ENTRIES_MAX) trapout(INVALID_WORD); - ram.lsp = 0; /* clear the literal stack */ + curtask->lsp = 0; /* clear the literal stack */ break; case INS_RET: /* jump to the instruction at ret stack */ - ram.pc = popRet(); + curtask->pc = popRet(); break; case INS_M2R: /* main -> ret stack */ pushRet(popMain()); @@ -465,7 +496,7 @@ void equi_main_loop() { case INS_STORE: /* main stack -> mem (word) */ pbuf = popMain(); pbuf2 = popMain(); - if(pbuf < write_protection_threshold) + if(!taskMemWriteAllowed(pbuf)) trapout(RESTRICTED_WRITE_ERROR); flatram[pbuf] = pbuf2 >> 8U; flatram[pbuf + 1U] = pbuf2 & 255U; @@ -478,45 +509,50 @@ void equi_main_loop() { pbuf = popMain(); break; case INS_DUP: /* ( a -- a a ) */ - if(ram.msp < 1) + if(curtask->msp < 1) trapout(STACK_UNDERFLOW); - pushMain(ram.main_stack[ram.msp-1]); + pushMain(curtask->main_stack[curtask->msp-1]); break; case INS_SWAP: /* ( a b -- b a ) */ - if(ram.msp < 2) + if(curtask->msp < 2) trapout(STACK_UNDERFLOW); - pbuf = ram.main_stack[ram.msp-2]; - ram.main_stack[ram.msp-2] = ram.main_stack[ram.msp-1]; - ram.main_stack[ram.msp-1] = pbuf; + pbuf = curtask->main_stack[curtask->msp-2]; + curtask->main_stack[curtask->msp-2] = curtask->main_stack[curtask->msp-1]; + curtask->main_stack[curtask->msp-1] = pbuf; break; case INS_ROT: /* ( a b c -- b c a ) */ - if(ram.msp < 3) + if(curtask->msp < 3) trapout(STACK_UNDERFLOW); - pbuf = ram.main_stack[ram.msp-3]; - pbuf2 = ram.main_stack[ram.msp-1]; - ram.main_stack[ram.msp-3] = ram.main_stack[ram.msp-2]; - ram.main_stack[ram.msp-2] = pbuf2; - ram.main_stack[ram.msp-1] = pbuf; + pbuf = curtask->main_stack[curtask->msp-3]; + pbuf2 = curtask->main_stack[curtask->msp-1]; + curtask->main_stack[curtask->msp-3] = curtask->main_stack[curtask->msp-2]; + curtask->main_stack[curtask->msp-2] = pbuf2; + curtask->main_stack[curtask->msp-1] = pbuf; break; case INS_OVER: /* ( a b -- a b a ) */ - if(ram.msp < 2) + if(curtask->msp < 2) trapout(STACK_UNDERFLOW); - pushMain(ram.main_stack[ram.msp-2]); + pushMain(curtask->main_stack[curtask->msp-2]); break; case INS_JUMP: /* unconditional signed jump: ( rel -- ) */ - ram.pc = (ushort) (ram.pc + (signed short) popMain()); + curtask->pc = (ushort) (curtask->pc + (signed short) popMain()); + if(!taskMemJumpAllowed(curtask->pc)) + trapout(OUT_OF_BOUNDS_JUMP); break; case INS_IF: /* conditional signed jump if cond is not zero: ( cond rel -- ) */ pbuf = popMain(); /* reladdr */ pbuf2 = popMain(); /* cond */ - if(pbuf2 != 0) - ram.pc = (ushort) (ram.pc + (signed short) pbuf); + if(pbuf2 != 0) { + curtask->pc = (ushort) (curtask->pc + (signed short) pbuf); + if(!taskMemJumpAllowed(curtask->pc)) + trapout(OUT_OF_BOUNDS_JUMP); + } break; case INS_EXPOINT: /* Locate execution point */ - pushMain(ram.pc + 1); + pushMain(curtask->pc + 1); break; case INS_GPDSTART: /* Locate GPD area start */ - pushMain(ram.gpd_start); + pushMain(curtask->gpd_start); break; case INS_GT: /* ( a b -- a>b ) */ pushMain((popMain() < popMain()) ? 1u : 0u); @@ -572,7 +608,7 @@ void equi_main_loop() { pushMain((ushort)cgetc()); break; case INS_PORTIO: /* ( p1 p2 port -- r1 r2 status ) - simulate/execute port I/O according to the spec */ - if(ram.msp < 3) + if(curtask->msp < 3) trapout(STACK_UNDERFLOW); portIO(popMain(), popMain(), popMain()); break; @@ -586,7 +622,7 @@ void equi_main_loop() { trapout(INVALID_INSTRUCTION); goto brx; } - if(ram.msp >= STACK_SIZE_WORDS || ram.rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */ + if(curtask->msp >= STACK_SIZE_WORDS || curtask->rsp >= STACK_SIZE_WORDS) /* check for stack overflow after any operation */ trapout(STACK_OVERFLOW); continue; brx: break; @@ -595,13 +631,12 @@ void equi_main_loop() { if(pfd) fclose(pfd); /* unset interpretation mode flag and exit */ ram.IM = 0; - ram.pc = ram.ibp = 65535U; + curtask->pc = ram.ibp = 65535U; /* clear all stacks and CLT */ - ram.msp = ram.rsp = ram.lsp = ram.cltp = 0; + curtask->msp = curtask->rsp = curtask->lsp = curtask->cltp = 0; } /* Equi VM entry point */ - int main(int argc, char* argv[]) { uchar instr, bc, smode = 0; /* detect host endianness */ @@ -617,20 +652,17 @@ int main(int argc, char* argv[]) { /* initialize the RAM in the most standard way */ ram.stack_size = STACK_SIZE_WORDS; ram.literal_stack_size = LIT_STACK_SIZE; - ram.clt_start = (uchar *)&ram.clt - (uchar *)&ram.stack_size; - ram.gpd_start = (uchar *)&ram.gpd - (uchar *)&ram.stack_size; - ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram.stack_size; + ram.cmd_start = (uchar *)&ram.cmdbuf - (uchar *)&ram; ram.cmd_size = CMD_BUF_SIZE; ram.II = ram.CM = ram.IM = ram.MM = 0; /* reset all flags */ - write_protection_threshold = ram.gpd_start; /* setup write protection */ + ram.taskcount = 0; /* process command line params */ if(argc > 1 && argv[1][0] == 'm') /* enter minification mode, don't run the programs */ ram.MM = 1; else if(argc > 1 && argv[1][0] == 's') /* enter silent mode, don't print the banners and prompts */ smode = 1; - /* Start both execution and input buffering from the start of command buffer (-1 because we use prefix increment) */ - ram.pc = ram.ibp = 65535U; - + /* Start input buffering from the start of command buffer (-1 because we use prefix increment) */ + ram.ibp = 65535U; if(!ram.MM && !smode) { /* skip the terminal init and the greeting if in minification or silent mode */ /* CC65-specific terminal init */ #ifdef __CC65__ @@ -639,16 +671,8 @@ int main(int argc, char* argv[]) { #else /* VT100-compatible terminal init */ printf("\033c"); #endif - printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022\nStack size: %d bytes\nLiteral stack size: %d bytes\nCLT: 0x%04X (%d bytes)\nGPD: 0x%04X (%d bytes)\nCommand buffer: 0x%04X (%d bytes)\nEqui ready\n\n> ", - STACK_SIZE, LIT_STACK_SIZE, ram.clt_start, ram.gpd_start - ram.clt_start, ram.gpd_start, ram.cmd_start - ram.gpd_start, ram.cmd_start, ram.cmd_size); - } - - /* now, if necessary, convert the values that could be read by the program to big-endian */ - if(host_islittle) { - ram.stack_size = tobig(ram.stack_size); - ram.clt_start = tobig(ram.clt_start); - ram.cmd_start = tobig(ram.cmd_start); - ram.cmd_size = tobig(ram.cmd_size); + printf("Welcome to Equi v" EQUI_VER " by Luxferre, 2022\nStack size: %d bytes\nLiteral stack size: %d bytes\nCommand buffer: 0x%04X (%d bytes)\nEqui ready\n\n> ", + STACK_SIZE, LIT_STACK_SIZE, ram.cmd_start, ram.cmd_size); } while(1) { /* Now, we're in the command mode loop */ @@ -683,6 +707,7 @@ int main(int argc, char* argv[]) { } ram.cmdbuf[++ram.ibp] = INS_QUIT; /* end program with INS_QUIT */ ram.IM = 1; /* set the mandatory interpretation mode flag */ + curtask = equi_load_task((ushort)ram.cmd_start, ram.ibp + 1); /* load the code as task 0 */ equi_main_loop(); /* and run the interpreter loop */ if(!smode) { cputc(CR); /* echo CR */