184 lines
7.7 KiB
JavaScript
184 lines
7.7 KiB
JavaScript
/*
|
|
* mu808: an ultralight numeric-only VM
|
|
* This is the core JS file for the reference implementation
|
|
* of mu808 VM for the Web and other ES5 engines
|
|
* See the README.md file in the repo root for all documentation
|
|
* Created by Luxferre in 2025, released into public domain
|
|
*/
|
|
|
|
/* Due to the modular nature, this file only defines the mu808 VM kernel
|
|
* which accepts all I/O routines as the parameters for its construction */
|
|
function createmu808(lineOut, charOut, lineIn) {
|
|
var MEMLIMIT = 16384,
|
|
PMEM = new Uint16Array(MEMLIMIT << 2), /* program memory */
|
|
DMEM = new Float64Array(MEMLIMIT), /* data memory */
|
|
runlimit = MEMLIMIT,
|
|
traceflag = false,
|
|
runcount = 0
|
|
|
|
/* memory range input function */
|
|
/* accepts the continuation callback as the last parameter */
|
|
function dataRangeInput(start, end, contfunc) {
|
|
(function inp(loc) {
|
|
lineIn(function(v) {
|
|
DMEM[loc] = parseFloat(v) || 0.0
|
|
if(loc < end) inp(loc+1)
|
|
else {
|
|
if(contfunc) contfunc()
|
|
}
|
|
})
|
|
})(start)
|
|
}
|
|
|
|
/* port output function */
|
|
function portout(port, data) {
|
|
if(port == 0) lineOut(data + "\n")
|
|
else if(port == 1) charOut(data & 255)
|
|
}
|
|
|
|
/* instruction line execution function (the main 808UL logic defined here) */
|
|
/* accepts the continuation callback as the last parameter */
|
|
function ilexec(lno, cmd, x, y, z, contfunc) {
|
|
var halt = 0, data_override = 0, bcheck, i, v1, v2, v3
|
|
while(halt == 0) {
|
|
v1 = v2 = v3 = data_override = DMEM[0] = 0 /* force the value at 0 to be always 0 */
|
|
if(lno == 0) halt = 1 /* immediate instruction */
|
|
if(runlimit > 0 && runcount > runlimit) {
|
|
halt = 1
|
|
if(traceflag) lineOut("Runlimit hit, halting...\n")
|
|
} else runcount++
|
|
if(traceflag)
|
|
lineOut("PC: " + lno + " INSTR: " + [cmd, x, y, z].join(" ") + "\n")
|
|
bcheck = (x < MEMLIMIT) && (y < MEMLIMIT) && (z < MEMLIMIT)
|
|
if(bcheck) { /* boundary checks passed */
|
|
v1 = DMEM[x]; v2 = DMEM[y]; v3 = DMEM[z] /* prefetch the memory values */
|
|
if(cmd == 1) { /* JMP */
|
|
if((v2 == 0 && x == 0) || (v2 > 0 && x == 1) || (v2 < 0 && x == 2)
|
|
|| (v2 >= 0 && x == 3) || (v2 <= 0 && x == 4) || (v2 != 0 && x == 5)
|
|
|| x == 6) { halt = 0; lno = z - 1 }
|
|
else if((v2 == 0 && x == 7) || (v2 > 0 && x == 8) || (v2 < 0 && x == 9)
|
|
|| (v2 >= 0 && x == 10) || (v2 <= 0 && x == 11) || (v2 != 0 && x == 12)
|
|
|| x == 13) { halt = 0; lno = (v3|0) - 1 }
|
|
} else if(cmd == 2) data_override = 1 /* IAT */
|
|
else if(cmd == 3) for(i=x;i<=y;i++) portout(z, DMEM[i]) /* OUT */
|
|
else if(cmd == 4) { /* INP */
|
|
dataRangeInput(x, y, function() {
|
|
/* continuation function duplicating what's after the switch */
|
|
/* except the data override branch */
|
|
lno++ /* increment the program counter */
|
|
if(lno >= MEMLIMIT || lno < 1 || (runlimit > 0 && runcount > runlimit)) {
|
|
if(traceflag) lineOut("Memory limit or runlimit hit, halting...\n")
|
|
halt = 1
|
|
} else { /* fetch the next instruction and execute it */
|
|
cmd = PMEM[lno << 2]
|
|
x = PMEM[(lno << 2) + 1]
|
|
y = PMEM[(lno << 2) + 2]
|
|
z = PMEM[(lno << 2) + 3]
|
|
ilexec(lno, cmd, x, y, z, contfunc)
|
|
}
|
|
})
|
|
return
|
|
} else if(cmd == 5) DMEM[z] = (x % 10000) + (y % 10000) / 10000.0 /* SET */
|
|
else if(cmd == 6) { /* CPY */
|
|
if(x == 0) DMEM[z] = y
|
|
else if(x == 1) DMEM[z] = v2
|
|
else if(v2 < MEMLIMIT && v3 < MEMLIMIT) DMEM[0|v3] = DMEM[0|v2]
|
|
} else if(cmd == 7) DMEM[z] = v1 + v2 * v3 /* FMA */
|
|
else if(cmd == 8) DMEM[z] = v1 - v2 /* SUB */
|
|
else if(cmd == 9) DMEM[z] = (v2 == 0) ? 0 : (v1 / v2) /* DIV */
|
|
else if(cmd == 10) DMEM[z] = (v2 == 0) ? (0|v1) : ((0|v1) % (0|v2)) /* MDF */
|
|
else if(cmd == 11) DMEM[z] = Math.abs(v2) /* ABS */
|
|
else if(cmd == 12) DMEM[z] = Math.sqrt(Math.abs(v2)) /* SQR */
|
|
else if(cmd == 13) { /* NEL */
|
|
if(x == 0) DMEM[z] = Math.exp(v2)
|
|
else DMEM[z] = (v2 == 0) ? 0 : Math.log(Math.abs(v2))
|
|
} else if(cmd == 14) { /* TRI */
|
|
if(x == 0) DMEM[z] = Math.sin(v2)
|
|
else if(x == 1) DMEM[z] = Math.cos(v2)
|
|
else DMEM[z] = Math.atan(v2)
|
|
} else if(cmd == 15) { /* RND */
|
|
v1 = 0|v1
|
|
DMEM[z] = v1 + (0|(Math.random() * ((0|v2) - v1 + 1)))
|
|
}
|
|
} /* command switch ends here */
|
|
lno++ /* increment the program counter */
|
|
if(lno >= MEMLIMIT || lno < 1 || (runlimit > 0 && runcount > runlimit)) {
|
|
if(traceflag) lineOut("Memory limit or runlimit hit, halting...\n")
|
|
halt = 1
|
|
} else { /* fetch the next instruction */
|
|
if(data_override && bcheck) {
|
|
cmd = PMEM[lno << 2]
|
|
x = (0|v1) % MEMLIMIT
|
|
y = (0|v2) % MEMLIMIT
|
|
z = (0|v3) % MEMLIMIT
|
|
} else {
|
|
cmd = PMEM[lno << 2]
|
|
x = PMEM[(lno << 2) + 1]
|
|
y = PMEM[(lno << 2) + 2]
|
|
z = PMEM[(lno << 2) + 3]
|
|
}
|
|
}
|
|
}
|
|
if(contfunc) contfunc()
|
|
}
|
|
|
|
/* instruction line entry function (interactive mode) */
|
|
/* accepts the continuation callback as the last parameter */
|
|
function ilenter(lno, cmd, x, y, z, contfunc) {
|
|
if(lno > 0) { /* record the instruction in memory */
|
|
PMEM[lno << 2] = cmd
|
|
PMEM[(lno << 2) + 1] = x
|
|
PMEM[(lno << 2) + 2] = y
|
|
PMEM[(lno << 2) + 3] = z
|
|
if(contfunc) contfunc()
|
|
} else if(lno == 0) { /* immediate execution */
|
|
runcount = 0
|
|
ilexec(lno, cmd, x, y, z, contfunc)
|
|
} else if(lno == -1) { /* display a range of instructions from cmd to x */
|
|
if(cmd < MEMLIMIT && x < MEMLIMIT) for(y=cmd;y<=x;y++) {
|
|
z = y << 2
|
|
lineOut("@" + y + ":\t" + [PMEM[z], PMEM[z+1], PMEM[z+2], PMEM[z+3]].join(' ') + '\n')
|
|
}
|
|
if(contfunc) contfunc()
|
|
} else if(lno == -2) { /* clear a range of data or instructions from x to y */
|
|
if(cmd < MEMLIMIT && x < MEMLIMIT) for(z=x;z<=y;z++) {
|
|
if(cmd == 0) PMEM[z*4] = PMEM[z*4+1] = PMEM[z*4+2] = PMEM[z*4+3] = 0
|
|
else DMEM[z] = 0.0
|
|
}
|
|
if(contfunc) contfunc()
|
|
} else if(lno == -3) { /* tracing on/off */
|
|
if(cmd == 0) {traceflag = 0; lineOut("Tracing off\n")}
|
|
else {traceflag = 1; lineOut("Tracing on\n")}
|
|
if(contfunc) contfunc()
|
|
} else if(lno == -4) { /* set the runlimit to the value of cmd */
|
|
runlimit = cmd
|
|
lineOut("Runlimit set to " + cmd + "\n")
|
|
if(contfunc) contfunc()
|
|
} else if(lno == -5) { /* can't exit to the environment, reset instead */
|
|
lineOut("Resetting the machine...\n")
|
|
for(y=0;y<MEMLIMIT;y++) {
|
|
DMEM[y] = 0.0
|
|
PMEM[y << 2] = 0
|
|
PMEM[(y << 2) + 1] = 0
|
|
PMEM[(y << 2) + 2] = 0
|
|
PMEM[(y << 2) + 3] = 0
|
|
}
|
|
runcount = 0
|
|
runlimit = MEMLIMIT
|
|
traceflag = false
|
|
lineOut("Machine reset complete\n")
|
|
if(contfunc) contfunc()
|
|
}
|
|
}
|
|
|
|
/* now, export the ilenter function as the outside API, as well as debug methods */
|
|
return {
|
|
ilenter: ilenter,
|
|
savemem: function() {return {"PMEM":PMEM,"DMEM":DMEM}},
|
|
loadmem: function(memobj) {
|
|
PMEM.set(memobj.PMEM)
|
|
DMEM.set(memobj.DMEM)
|
|
}
|
|
}
|
|
}
|