µEForth on the Web 🕸 🌎 🕸 July 23, 2022 µEForth and ESP32forth ✨ ✨ ✨ • An EForth with minimal non-Forth • Minimal redundancy • Kernel compiled from inline Forth text • Support for Window, Linux, ESP32, and now Web! Why the Web? 🕸 🌎 🕸 • Portable, powerful, relatively simple I/O APIs. • Asm.js, WebAssembly, and JITs mean it can go fast! • It's easy to show people. • It let's people run my code without trusting me. 🙊 µEForth Approach ⚓ • Each opcodes in 1-2 lines of C code • Define "system variables" in a C structure • Define 5 "core" opcodes in larger C functions ➥ These could be built from opcodes ➥ But avoid needing to "assemble" loops • Boot from Forth code in a string Design Choices 😎 • Indirect Threaded ➥ Single cell VM opcodes ➥ Simpler SEE / DOES> ➥ Computed goto in C avoids assembly ➥ Other models might be faster? • Unlimited stacks • 32-bit floats only • No counted strings How to Build a Forth! 🧱 Decide Your Registers ® ® ® ® register cell_t *ip, *rp, *sp, tos, w; register float *fp, ft; ip ➵ Instruction Pointer rp ➵ Return Stack Pointer sp ➵ Data Stack Pointer tos ➵ Top of Stack w ➵ Work / Last Word fp ➵ Float Stack Pointer ft ➵ Temp Float Define Common Operations ⊛ #define NIP (--sp) #define NIPn(n) (sp -= (n)) #define DROP (tos = *sp--) #define DROPn(n) (NIPn(n-1), DROP) #define DUP (*++sp = tos) #define PUSH DUP; tos = (cell_t) Be able to Park/Unpark 🏞 run(rp) { UNPARK; ..interpret.. PARK; return rp; } Be able to Park/Unpark 🏞 #define PARK DUP; *++rp = (cell_t) fp; *++rp = (cell_t) sp; *++rp = (cell_t) ip #define UNPARK ip = (cell_t *) *rp--; sp = (cell_t *) *rp--; fp = (float *) *rp--; DROP X-Macros ⚔ • Define a list of macro calls • Encode a table of information • Reuse portions of the list in multiple places Struture of an Opcode ⌱ X("myop!", MY_OP_C_NAME, tos = my_code) \ enum { #define X(name, op, code) OP_ ## op, PLATFORM_OPCODE_LIST #undef X }; switch (*(cell_t *) w & 0xff) { #define X(name, op, code) case OP_ ## op: { code; } NEXT; PLATFORM_OPCODE_LIST #undef X } Alternate Syntax ⛕ #define Y(name, code) X(#name, name, code) #define OPCODES \ Y(AND, tos &= *sp--) \ Y(OR, tos |= *sp--) \ Y(XOR, tos ˆ= *sp--) \ X("DUP", ALTDUP, DUP) \ Y(SWAP, w = tos; tos = *sp; *sp = w) \ Y(OVER, DUP; tos = sp[-1]) \ X("C@", CAT, tos = *(uint8_t *) tos) \ X("!", STORE, *(cell_t *) tos = *sp--; DROP) \ X("C!", CSTORE, *(uint8_t *) tos = *sp--; DROP) \ X("SP@", SPAT, DUP; tos = (cell_t) sp) \ X("SP!", SPSTORE, sp = (cell_t *) tos; DROP) \ X("RP@", RPAT, DUP; tos = (cell_t) rp) \ X("RP!", RPSTORE, rp = (cell_t *) tos; DROP) \ X(">R", TOR, *++rp = tos; DROP) \ X("R>", FROMR, DUP; tos = *rp; --rp) \ X("R@", RAT, DUP; tos = *rp) \ With Vocabularies ⌱ X("name", NAME, code) Y(name, code) XV(forth_and_vocab, "name", NAME, code) YV(forth_and_vocab, NAME, code) : QUIT BEGIN EVALUATE1 AGAIN ; S>NUMBER? 🡆 BASE PARSE 🡆 'TIB #TIB >IN CREATE 🡆 CURRENT FIND 🡆 CONTEXT EVALUATE1 🡆 STATE 'NOTFOUND static int match(char sep, char ch) { return sep == ch || (sep == ' ' && (ch == '\t' || ch == '\n' || ch == '\r')); } static cell_t parse(cell_t sep, cell_t *ret) { if (sep == ' ') { while (g_sys->tin < g_sys->ntib && match(sep, g_sys->tib[g_sys->tin])) { ++g_sys->tin; } } cell_t start = g_sys->tin; while (g_sys->tin < g_sys->ntib && !match(sep, g_sys->tib[g_sys->tin])) { ++g_sys->tin; } cell_t len = g_sys->tin - start; if (g_sys->tin < g_sys->ntib) { ++g_sys->tin; } *ret = (cell_t) (g_sys->tib + start); return len; } const char boot[] = R"""( : ( 41 parse drop drop ; immediate : \ 10 parse drop drop ; immediate : #! 10 parse drop drop ; immediate ( shebang for scripts ) ( Now can do comments! ) ( Useful Basic Compound Words ) : nip ( a b -- b ) swap drop ; : rdrop ( r: n n -- ) r> r> drop >r ; : */ ( n n n -- n ) */mod nip ; : * ( n n -- n ) 1 */ ; : /mod ( n n -- n n ) 1 swap */mod ; : / ( n n -- n ) /mod nip ; : mod ( n n -- n ) /mod drop ; : invert ( n -- ˜n ) -1 xor ; : negate ( n -- -n ) invert 1 + ; : - ( n n -- n ) negate + ; How to bring it to the Web? ⛵ 🌎 🕸 • Convert to Asm.js ➥ Hand convert 5 "core" words ➥ Automate the opcodes Asm.js ⛵🐱 • Best hack ever • Embed C in JavaScript • Converted by Chrome to WebAssembly Asm.js ⛵🐱 a = a | 0; // a is an int a = fround(a); // a is a float32 a = (a + 1) | 0; // ++a; a = (a + b) | 0; // a = a + b; a = imul32(a, b); // a = a * b; a = i32[p>>2] | 0; // a = *p; (32-bit) a = u8[p] | 0; // a = *p; (8-bit) return a | 0; // function returns an int function Match(sep, ch) { return sep == ch || (sep == 32 && (ch == 9 || ch == 10 || ch == 13)); } function Parse(sep, ret) { if (sep == 32) { while (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2] && Match(sep, u8[i32[g_sys_tib>>2] + i32[g_sys_tin>>2]])) { ++i32[g_sys_tin>>2]; } } var start = i32[g_sys_tin>>2]; while (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2] && !Match(sep, u8[i32[g_sys_tib>>2] + i32[g_sys_tin>>2]])) { ++i32[g_sys_tin>>2]; } var len = i32[g_sys_tin>>2] - start; if (i32[g_sys_tin>>2] < i32[g_sys_ntib>>2]) { ++i32[g_sys_tin>>2]; } i32[ret>>2] = i32[g_sys_tib>>2] + start; if (DEBUGGING) { console.log(’PARSE: [’ + GetString(i32[ret>>2], len) + ’]’); } return len; } Makefiles and Nodejs ☋ ☊ • Glue together C without includes • Allows one big .ino file for ESP32 • Replace patterns in opcodes with Asm.js cases = ReplaceAll(cases, ’tos += *sp--’, ’tos = (tos + *sp)|0; --sp’); cases = ReplaceAll(cases, ’tos = (*sp--) - tos’, ’tos = (*sp - tos)|0; --sp’); cases = ReplaceAll(cases, ’tos *= *sp--’, ’tos = imul(tos, *sp); --sp’); cases = ReplaceAll(cases, ’++tos’, ’tos = (tos + 1)|0’); cases = ReplaceAll(cases, ’--tos’, ’tos = (tos - 1)|0’); Regex Folly 🙃 • Compilation by Regex works... poorly • 140 lines of replacements, for 150 lines base opcodes • Probably should revisit Talking to the Outside ☎️ #define PLATFORM_OPCODE_LIST \ X("CALL", CALL, DUP; sp = Call(sp|0)|0; DROP) \ Talking to the Outside ☎️ var objects = [SetEval]; function SetEval(sp) { var index = i32[sp>>2]; sp -= 4; var n = i32[sp>>2]; sp -= 4; var a = i32[sp>>2]; sp -= 4; objects[index] = eval(GetString(a, n)); return sp; } function Call(sp) { var op = i32[sp>>2]; sp -= 4; return objects[op](sp); } r| (function(sp) { if (globalObj[’readline’]) { return -1; } sp += 4; i32[sp>>2] = globalObj.inbuffer.length ? -1 : 0; return sp; }) | 4 jseval! : web-key? ( -- f ) yield 4 call ; ’ web-key? is key? <div id="ueforth"></div> <script src="ueforth.js"></script> Caveats 😈 👿 • */MOD opcode is a lie • Asm.js heap is fixed sized • Terminal is too slow • I lied, S>FLOAT? is "core" too function SSMOD(sp) { var a = i32[(sp - 8)>>2]; var b = i32[(sp - 4)>>2]; var c = i32[sp>>2]; a *= b; var x = Math.floor(a / c); var m = a - x * c; if (m < 0) { x--; m += c; } i32[(sp - 8)>>2] = m; i32[sp>>2] = x; } Happy Things ◔‿◔ • Basic graphics already added • Asm.js memory passable to WebGL DEMO & QUESTIONS? 🏵 Thank you!