µ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!