aboutsummaryrefslogtreecommitdiff
path: root/src/micro
diff options
context:
space:
mode:
Diffstat (limited to 'src/micro')
-rw-r--r--src/micro/micro.c643
-rw-r--r--src/micro/micro_port.h89
-rw-r--r--src/micro/port_linux.c284
-rw-r--r--src/micro/port_stub.c59
-rw-r--r--src/micro/port_wasm_stream.c121
-rw-r--r--src/micro/port_win.c268
6 files changed, 1464 insertions, 0 deletions
diff --git a/src/micro/micro.c b/src/micro/micro.c
new file mode 100644
index 0000000..4542bd8
--- /dev/null
+++ b/src/micro/micro.c
@@ -0,0 +1,643 @@
+/* portable microcore. the base machine from core.c — memory, the two stacks,
+ the 22 opcodes, call/ret, and the genome — without the jit, llvm, o3, or
+ the enhancement optimizer.
+
+ `/` `%` `<<` and every other custom opcode are installed at runtime
+ (opcode 21). the genome's own idiv, imod, ishl, lfa and outside arrive
+ that way, and so do programs that declare enhance. this file lowers a
+ recipe or a straight-line vm body into a flat list of micro-ops and runs
+ that list. there is no constant folding and no multiply-high rewrite of
+ division; the results match, the list is just longer. install kind 2 is
+ an o3 region: it is accepted and ignored, and the ordinary bytecode of
+ that region runs instead, which is the same program the full interpreter
+ would run if the region did not lift.
+
+ no libc in this file. stdint.h is freestanding. 64-bit division is the
+ compiler's runtime. bss must be zero before micro_run.
+
+ memory size and the map come from memmap.h (the default is 1,048,576
+ words). a smaller board compiles this file against the headers src/tools/genmap
+ writes, and against the genome built for that same map. */
+#include <stdint.h>
+#ifdef USE_LSP_GENOME
+#include "lsp_genome.h"
+#elif defined(GENOME_H)
+#include GENOME_H
+#else
+#include "genome.h"
+#endif
+#include "micro_port.h"
+#include "fops.h"
+#include "bops.h"
+#define NENH 512
+#define XOP0 22
+#define XPOOL 4096
+#define MAXOPS 128
+#define MAXV 256
+
+enum {
+ U_LI, U_MOV, U_ADD, U_SUB, U_MUL, U_SDIV, U_UDIV, U_SREM, U_UREM,
+ U_AND, U_OR, U_XOR, U_SHL, U_SHR, U_SAR, U_NEG, U_NOT,
+ U_LT, U_LTU, U_EQ, U_NE, U_MIN, U_MAX, U_MINU, U_MAXU,
+ U_MULHS, U_MULHU, U_CLZ, U_ABS, U_SEL, U_LD, U_ST,
+ U_CTZ, U_POPCNT, U_BSWAP, U_ROTL, U_ROTR, U_F2S, U_S2F, U_N
+};
+static const char *const uname[U_N] = {
+ "li", "mov", "add", "sub", "mul", "sdiv", "udiv", "srem", "urem",
+ "and", "or", "xor", "shl", "shr", "sar", "neg", "not",
+ "lt", "ltu", "eq", "ne", "min", "max", "minu", "maxu",
+ "mulhs", "mulhu", "clz", "abs", "sel", "ld", "st",
+ "ctz", "popcnt", "bswap", "rotl", "rotr", "f2s", "s2f"
+};
+static const signed char uarity[U_N] = {
+ 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 1, 2,
+ 1, 1, 1, 2, 2, 1, 1
+};
+
+typedef struct {
+ unsigned char op, a, b, c;
+ unsigned short d;
+ Cell imm;
+} Xop;
+typedef struct {
+ unsigned char live, nin, nout;
+ unsigned short n, at, nv;
+ unsigned short out[8];
+} Enh;
+
+/* the machine: m is mw words, and grows on demand through the port (micro_mem_alloc).
+ every access goes through m and mw, which the port's memory may move under. */
+static Cell *m;
+static UCell mw;
+static UCell memory_initial = MICRO_MW_INIT, memory_limit = MICRO_MW_LIMIT;
+int micro_memory_config(UCell initial, UCell limit)
+{
+ if (m || initial < MICRO_MW_INIT || limit < initial || limit > 0x7fffffff) return 0;
+ memory_initial = initial; memory_limit = limit; return 1;
+}
+static Xop xops[XPOOL];
+static int xuse;
+static Enh enh[NENH];
+static Xop build[MAXOPS];
+static int bn, bnv, berr;
+static Cell xv[MAXV];
+static int jflag;
+static int jknown; /* 0 until something asks for the end-of-input flag: the probe reads the port, so it waits for the first ask */
+
+UCell micro_mem_words(void) { return mw; }
+static int inmem(Cell a) { return (UCell)a < mw; }
+static Cell ld(Cell a) { return inmem(a) ? m[a] : 0; }
+static Cell b_load(Cell addr, int n)
+{
+ if (!bop_ok(addr, n, mw)) return 0;
+ return bop_load(m, addr, n);
+}
+static void b_store(Cell addr, Cell val, int n)
+{
+ Cell out[2], w0;
+ int nw, i;
+ if (!bop_ok(addr, n, mw)) return;
+ nw = bop_merge(m, addr, val, n, out, &w0);
+ for (i = 0; i < nw; i++) if (inmem(w0 + i)) m[w0 + i] = out[i];
+}
+
+/* the run's sentinel pcs: each names why a run stopped that is not a halt. they
+ are negative, so no address in memory is one of them. */
+#define PC_DATA_OVERFLOW (-3)
+#define PC_RETURN_OVERFLOW (-4)
+#define PC_PAST_END (-5)
+#define PC_BAD_OP (-6)
+#define PC_HALT (-7) /* the zero word: the program's halt, not an error */
+static int bad_op;
+
+/* grows the machine to at least words words, through the port. the contents stay,
+ and the new words are zero. 1 on success, 0 when the port has no more memory. */
+static int mem_grow_words(UCell words)
+{
+ Cell *n;
+ if (words <= mw && m) return 1;
+ if (words > memory_limit) return 0;
+ n = micro_mem_alloc(words);
+ if (!n) return 0;
+ m = n;
+ mw = words;
+ return 1;
+}
+
+/* the heap asks for more memory (syscall 9, a = 4, b = words). answers the new
+ size in words, or -1 when the port cannot give it */
+static Cell mem_grow_request(Cell words)
+{
+ if (words < 0) return -1;
+ if (!mem_grow_words((UCell)words)) return -1;
+ return (Cell)mw;
+}
+static int stack_memory_grow(UCell words) { return mem_grow_words(words); }
+#include "stack.h"
+
+static Cell mulhu(UCell a, UCell b)
+{
+ UCell a0 = a & 0xffffffffull, a1 = a >> 32, b0 = b & 0xffffffffull, b1 = b >> 32;
+ UCell p0 = a0 * b0, p1 = a1 * b0, p2 = a0 * b1, p3 = a1 * b1;
+ UCell mid = (p0 >> 32) + (p1 & 0xffffffffull) + (p2 & 0xffffffffull);
+ return (Cell)(p3 + (p1 >> 32) + (p2 >> 32) + (mid >> 32));
+}
+static Cell mulhs(Cell a, Cell b)
+{
+ Cell hi = mulhu((UCell)a, (UCell)b);
+ if (a < 0) hi -= b;
+ if (b < 0) hi -= a;
+ return hi;
+}
+static Cell sar(Cell v, int n)
+{
+ if (n <= 0) return v;
+ if (v < 0) return (Cell)(((UCell)v >> n) | (~(UCell)0 << (64 - n)));
+ return (Cell)((UCell)v >> n);
+}
+static Cell ev(int op, Cell a, Cell b, Cell c)
+{
+ UCell ua = (UCell)a, ub = (UCell)b;
+ int sh = (int)(b & 63);
+ switch (op) {
+ case U_MOV: return a;
+ case U_ADD: return (Cell)(ua + ub);
+ case U_SUB: return (Cell)(ua - ub);
+ case U_MUL: return (Cell)(ua * ub);
+ case U_SDIV: return b == 0 ? 0 : (a == (Cell)((UCell)1 << 63) && b == -1) ? a : a / b;
+ case U_UDIV: return b == 0 ? 0 : (Cell)(ua / ub);
+ case U_SREM: return b == 0 ? a : (a == (Cell)((UCell)1 << 63) && b == -1) ? 0 : a % b;
+ case U_UREM: return b == 0 ? a : (Cell)(ua % ub);
+ case U_AND: return a & b;
+ case U_OR: return a | b;
+ case U_XOR: return a ^ b;
+ case U_SHL: return (Cell)(ua << sh);
+ case U_SHR: return (Cell)(ua >> sh);
+ case U_SAR: return sar(a, sh);
+ case U_NEG: return (Cell)(0ull - ua);
+ case U_NOT: return ~a;
+ case U_LT: return a < b;
+ case U_LTU: return ua < ub;
+ case U_EQ: return a == b;
+ case U_NE: return a != b;
+ case U_MIN: return a < b ? a : b;
+ case U_MAX: return a > b ? a : b;
+ case U_MINU: return ua < ub ? a : b;
+ case U_MAXU: return ua > ub ? a : b;
+ case U_MULHS: return mulhs(a, b);
+ case U_MULHU: return mulhu(ua, ub);
+ case U_CLZ: {
+ int n = 0;
+ if (!ua) return 64;
+ while (!(ua & ((UCell)1 << 63))) { ua <<= 1; n++; }
+ return n;
+ }
+ case U_ABS: return a < 0 ? (Cell)(0ull - ua) : a;
+ case U_CTZ: { int n = 0; if (!ua) return 64; while (!(ua & 1)) { ua >>= 1; n++; } return n; }
+ case U_POPCNT: { int n = 0; while (ua) { ua &= ua - 1; n++; } return n; }
+ case U_BSWAP: { UCell r = 0; int i; for (i = 0; i < 8; i++) { r = (r << 8) | (ua & 0xff); ua >>= 8; } return (Cell)r; }
+ case U_ROTL: return (Cell)((ua << sh) | (ua >> ((64 - sh) & 63)));
+ case U_ROTR: return (Cell)((ua >> sh) | (ua << ((64 - sh) & 63)));
+ case U_F2S: return (Cell)fop_f2s((uint64_t)a);
+ case U_S2F: return (Cell)fop_s2f((uint64_t)a);
+ case U_SEL: return a ? b : c;
+ }
+ return 0;
+}
+
+static int vnew(int op, int a, int b, int c, Cell imm)
+{
+ Xop *u;
+ if (berr) return 0;
+ if (bn >= MAXOPS || bnv >= MAXV) { berr = -5; return 0; }
+ u = &build[bn++];
+ u->op = (unsigned char)op;
+ u->a = (unsigned char)a;
+ u->b = (unsigned char)b;
+ u->c = (unsigned char)c;
+ u->d = (unsigned short)bnv;
+ u->imm = imm;
+ return bnv++;
+}
+static int seq(const char *a, const char *b)
+{
+ while (*a && *a == *b) { a++; b++; }
+ return *a == *b;
+}
+static int findop(const char *s)
+{
+ int i;
+ for (i = 0; i < U_N; i++) if (seq(uname[i], s)) return i;
+ return -1;
+}
+static int parse_num(const char *t, Cell *o)
+{
+ int sign = 1, base = 10, d;
+ UCell n = 0;
+ if (*t == '-') { sign = -1; t++; }
+ else if (*t == '+') t++;
+ if (*t == '0' && (t[1] == 'x' || t[1] == 'X')) { base = 16; t += 2; }
+ else if (*t == '0' && t[1]) base = 8;
+ if (!*t) return -1;
+ for (; *t; t++) {
+ if (*t >= '0' && *t <= '9') d = *t - '0';
+ else if (*t >= 'a' && *t <= 'f') d = *t - 'a' + 10;
+ else if (*t >= 'A' && *t <= 'F') d = *t - 'A' + 10;
+ else return -1;
+ if (d >= base) return -1;
+ n = n * (UCell)base + (UCell)d;
+ }
+ *o = sign < 0 ? (Cell)(0ull - n) : (Cell)n;
+ return 0;
+}
+static int regn(const char *t)
+{
+ int r = 0;
+ if (t[0] != 'r' || t[1] < '0' || t[1] > '9') return -1;
+ t++;
+ while (*t >= '0' && *t <= '9') {
+ r = r * 10 + *t++ - '0';
+ if (r > 15) return -2;
+ }
+ return r;
+}
+static int named(Cell names, int nin, const char *t)
+{
+ int i, j;
+ Cell s;
+ for (i = 0; i < nin && names; i++) {
+ s = ld(names + i);
+ j = 0;
+ while (ld(s + j) && ld(s + j) == (unsigned char)t[j]) j++;
+ if (!ld(s + j) && !t[j]) return i;
+ }
+ return -1;
+}
+static int rget(int *cur, int r)
+{
+ if (cur[r] < 0) cur[r] = vnew(U_LI, 0, 0, 0, 0);
+ return cur[r];
+}
+
+static int parse_recipe(Cell src, Cell names, int nin, int nout, int *out)
+{
+ int cur[16], i, n, k, op, args[3], na, dst, isst, nt, rr, v;
+ char tok[8][32];
+ Cell p = src, x;
+ for (i = 0; i < 16; i++) cur[i] = i < nin ? i : -1;
+ bn = 0; bnv = nin; berr = 0;
+ for (;;) {
+ nt = 0;
+ for (;;) {
+ int c = (int)ld(p);
+ while (c == ' ' || c == '\t' || c == '\r') c = (int)ld(++p);
+ if (!c || c == '\n' || c == ';') break;
+ if (c == '=') {
+ if (nt < 8) { tok[nt][0] = '='; tok[nt][1] = 0; nt++; }
+ p++;
+ continue;
+ }
+ n = 0;
+ while (c && c != ' ' && c != '\t' && c != '\n' && c != ';' && c != '=' && c != '\r') {
+ if (n < 31) tok[nt < 8 ? nt : 7][n++] = (char)c;
+ c = (int)ld(++p);
+ }
+ if (nt < 8) tok[nt++][n] = 0;
+ }
+ if (nt) {
+ isst = seq(tok[0], "st");
+ if (!isst && (nt < 3 || !seq(tok[1], "="))) return -2;
+ dst = -1;
+ if (!isst) {
+ rr = regn(tok[0]);
+ if (rr == -2) return -2;
+ dst = rr >= 0 ? rr : named(names, nin, tok[0]);
+ if (dst < 0 || dst > 15) return -2;
+ }
+ k = isst ? 1 : 2;
+ op = -1;
+ if (!isst && nt > 3) {
+ op = findop(tok[k]);
+ if (op < 0) return -2;
+ k++;
+ }
+ if (isst) op = U_ST;
+ na = 0;
+ for (; k < nt; k++) {
+ char *t = tok[k];
+ v = -1;
+ if (na >= 3) return -2;
+ rr = regn(t);
+ if (rr == -2) return -2;
+ if (rr >= 0) v = rget(cur, rr);
+ else if ((t[0] >= '0' && t[0] <= '9') || t[0] == '-' || t[0] == '+') {
+ if (parse_num(t, &x)) return -2;
+ v = vnew(U_LI, 0, 0, 0, x);
+ } else {
+ i = named(names, nin, t);
+ if (i < 0) return -2;
+ v = rget(cur, i);
+ }
+ if (berr) return berr;
+ args[na++] = v;
+ }
+ if (op < 0) {
+ if (na != 1) return -2;
+ cur[dst] = args[0];
+ } else {
+ if (na != (op == U_LI ? 0 : uarity[op])) {
+ if (!(op == U_LI && na == 1)) return -2;
+ }
+ if (op == U_LI) cur[dst] = na ? args[0] : vnew(U_LI, 0, 0, 0, 0);
+ else if (op == U_MOV) { if (na != 1) return -2; cur[dst] = args[0]; }
+ else if (op == U_ST) vnew(U_ST, args[0], args[1], 0, 0);
+ else cur[dst] = vnew(op, args[0], na > 1 ? args[1] : 0, na > 2 ? args[2] : 0, 0);
+ if (berr) return berr;
+ }
+ }
+ if (!ld(p)) break;
+ p++;
+ }
+ for (i = 0; i < nout; i++) {
+ if (cur[i] < 0) cur[i] = vnew(U_LI, 0, 0, 0, 0);
+ out[i] = cur[i];
+ }
+ return berr;
+}
+
+static int lower_vm(Cell a, Cell len, int nin, int nout, int *out)
+{
+ static const int map[] = { U_ADD, U_SUB, U_MUL, U_AND, U_OR, U_XOR, U_SAR, U_LT };
+ int st[256], sp = 0, i, k, x, y, j;
+ Cell w;
+ if ((UCell)len > 100000) return -5;
+ bn = 0; bnv = nin; berr = 0;
+ for (i = 0; i < nin; i++) st[sp++] = i;
+ for (i = 0; i < (int)len; i++) {
+ w = ld(a + i);
+ if (w & 1) {
+ if (sp >= 256) return -4;
+ st[sp++] = vnew(U_LI, 0, 0, 0, w >> 1);
+ if (berr) return berr;
+ continue;
+ }
+ k = (int)(w >> 1);
+ if (k == 9) break;
+ switch (k) {
+ case 1:
+ if (sp < 1) return -4;
+ st[sp - 1] = vnew(U_LD, st[sp - 1], 0, 0, 0);
+ break;
+ case 2:
+ if (sp < 2) return -4;
+ vnew(U_ST, st[sp - 1], st[sp - 2], 0, 0);
+ sp -= 2;
+ break;
+ case 3:
+ if (sp < 1) return -4;
+ x = st[sp - 1];
+ if (x < nin) return -4;
+ for (j = 0; j < bn && build[j].d != (unsigned)x; j++);
+ if (j == bn || build[j].op != U_LI) return -4;
+ y = (int)build[j].imm;
+ if (y < 0 || y >= sp - 1) return -4;
+ st[sp - 1] = st[sp - 2 - y];
+ break;
+ case 4:
+ if (sp < 1) return -4;
+ sp--;
+ break;
+ case 5:
+ if (sp < 2) return -4;
+ x = st[sp - 1]; st[sp - 1] = st[sp - 2]; st[sp - 2] = x;
+ break;
+ case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20:
+ if (sp < 2) return -4;
+ x = st[sp - 1]; y = st[sp - 2]; sp -= 2;
+ st[sp++] = vnew(map[k - 13], y, x, 0, 0);
+ break;
+ default:
+ return -4;
+ }
+ if (berr) return berr;
+ }
+ if (sp != nout) return -4;
+ for (i = 0; i < nout; i++) out[i] = st[i];
+ return berr;
+}
+
+static int commit(int slot, int nin, int nout, int *out)
+{
+ Enh *e = &enh[slot];
+ int base = xuse, i;
+ if (berr) return berr;
+ if (e->live && (int)e->at + (int)e->n == xuse) base = e->at;
+ if (base + bn > XPOOL) return -5;
+ if (e->live && (int)e->at + (int)e->n == xuse) xuse = e->at;
+ e->at = (unsigned short)xuse;
+ e->n = (unsigned short)bn;
+ e->nv = (unsigned short)bnv;
+ e->nin = (unsigned char)nin;
+ e->nout = (unsigned char)nout;
+ for (i = 0; i < nout; i++) e->out[i] = (unsigned short)out[i];
+ for (i = 0; i < bn; i++) xops[xuse++] = build[i];
+ e->live = 1;
+ return 0;
+}
+
+static int install(Cell d)
+{
+ Cell slot, kind, nin, nout, pay, len, names;
+ int out[8], r;
+ if (!inmem(d) || !inmem(d + 6)) return -1;
+ slot = m[d]; kind = m[d + 1]; nin = m[d + 2]; nout = m[d + 3];
+ pay = m[d + 4]; len = m[d + 5]; names = m[d + 6];
+ if (kind == 2) return 0;
+ if ((UCell)slot >= NENH || (UCell)nin > 8 || (UCell)nout > 8 || (kind != 0 && kind != 1)) return -1;
+ r = kind == 0 ? parse_recipe(pay, names, (int)nin, (int)nout, out)
+ : lower_vm(pay, len, (int)nin, (int)nout, out);
+ if (r) return r;
+ return commit((int)slot, (int)nin, (int)nout, out);
+}
+
+static int xrun(Enh *e, int s)
+{
+ int i, j;
+ Xop *u;
+ for (i = 0; i < e->nv && i < MAXV; i++) xv[i] = 0;
+ for (j = 0; j < e->nin; j++) xv[j] = m[s + e->nin - 1 - j];
+ for (i = 0; i < e->n; i++) {
+ u = &xops[e->at + i];
+ if (u->op == U_LI) xv[u->d] = u->imm;
+ else if (u->op == U_LD) xv[u->d] = ld(xv[u->a]);
+ else if (u->op == U_ST) { if (inmem(xv[u->a])) m[xv[u->a]] = xv[u->b]; }
+ else xv[u->d] = ev(u->op, xv[u->a], xv[u->b], xv[u->c]);
+ }
+ s = s + e->nin - e->nout;
+ for (j = 0; j < e->nout; j++) m[s + e->nout - 1 - j] = xv[e->out[j]];
+ return s;
+}
+
+/* a run suspends when the port has no input yet but more may come (streaming,
+ see micro_port.h): micro_getc returns MICRO_WAIT, the getc is run again on
+ micro_resume, and the stacks wait where they are */
+static int sus_p, sus_s, sus_r;
+
+static int run_from(int p, int s, int r)
+{
+ int op;
+ Cell w, a, b;
+ while ((UCell)p < mw) {
+ if (s < stack_data_limit() || r > stack_return_limit()) {
+ Cell sp = s, rp = r; int ret = r > stack_return_limit();
+ if (!stack_control || stack_expand(ret, stack_get(ret ? SC_RETURN_TOP : SC_DATA_TOP) - stack_get(ret ? SC_RETURN_LOW : SC_DATA_LOW) + 1, &sp, &rp) < 0) {
+ p = ret ? PC_RETURN_OVERFLOW : PC_DATA_OVERFLOW; break;
+ }
+ s = (int)sp; r = (int)rp;
+ }
+ w = m[p++];
+ if (w & 1) { m[--s] = w >> 1; continue; }
+ a = m[s]; b = m[s + 1];
+ switch ((int)(w >> 1)) {
+ case 1: m[s] = ld(a); break;
+ case 2:
+ if (a == SYSGATE) {
+ /* a call can grow the memory and move m, so the result is stored after the call */
+ Cell result;
+ if (b == 9 && m[a + 1] == 6) result = stack_register(m[a + 2]) ? 0 : -1;
+ else if (b == 9 && (m[a + 1] == 7 || m[a + 1] == 8)) {
+ Cell sp = s + 2, rp = r;
+ result = stack_expand(m[a + 1] == 8, m[a + 2], &sp, &rp);
+ s = (int)sp; r = (int)rp; m[a] = result; break;
+ }
+ else if (b == 9 && m[a + 1] == 9) result = m[a + 2] > 0 && (UCell)m[a + 2] <= mw ? (Cell)mw : -1;
+ else if (b == 9 && m[a + 1] == 4) result = mem_grow_request(m[a + 2]);
+ else if (b == 9 && m[a + 1] == 5) {
+ Cell limit = m[a + 2];
+ if (limit < 0 || (UCell)limit < mw || limit > 0x7fffffff) result = -1;
+ else { memory_limit = (UCell)limit; result = limit; }
+ } else result = micro_syscall(m, b, m[a + 1], m[a + 2], m[a + 3]);
+ m[a] = result;
+ s += 2;
+ break;
+ }
+ if (inmem(a)) m[a] = b;
+ s += 2;
+ break;
+ case 3: m[s] = ld((Cell)s + 1 + a); break;
+ case 4: s++; break;
+ case 5: m[s] = b; m[s + 1] = a; break;
+ case 6: m[r++] = p; p = (int)a; s++; break;
+ case 7: p = (int)a; s++; break;
+ case 8: if (!b) p = (int)a; s += 2; break;
+ case 9: p = (int)m[--r]; break;
+ case 10: {
+ int ch = micro_getc();
+ if (ch == MICRO_WAIT) { sus_p = p - 1; sus_s = s; sus_r = r; return MICRO_SUSPENDED; }
+ m[--s] = (Cell)(signed char)ch;
+ jflag = micro_eof();
+ jknown = 1;
+ break;
+ }
+ case 11:
+ if (!jknown) { jflag = micro_eof(); jknown = 1; }
+ m[--s] = jflag;
+ break;
+ case 12: micro_putc((int)a); s++; break;
+ case 13: m[++s] = b + a; break;
+ case 14: m[++s] = b - a; break;
+ case 15: m[++s] = b * a; break;
+ case 16: m[++s] = b & a; break;
+ case 17: m[++s] = b | a; break;
+ case 18: m[++s] = b ^ a; break;
+ case 19: m[++s] = sar(b, (int)(a & 63)); break;
+ case 20: m[++s] = b < a; break;
+ case 21: m[s] = install(a); break;
+ case 600:
+ if ((UCell)p >= mw) { p = PC_PAST_END; break; }
+ m[--s] = m[p++];
+ break;
+ case 601: m[++s] = (Cell)fop_add((uint64_t)b, (uint64_t)a); break;
+ case 602: m[++s] = (Cell)fop_sub((uint64_t)b, (uint64_t)a); break;
+ case 603: m[++s] = (Cell)fop_mul((uint64_t)b, (uint64_t)a); break;
+ case 604: m[++s] = (Cell)fop_div((uint64_t)b, (uint64_t)a); break;
+ case 605: m[++s] = fop_eq((uint64_t)b, (uint64_t)a); break;
+ case 606: m[++s] = fop_lt((uint64_t)b, (uint64_t)a); break;
+ case 607: m[++s] = fop_le((uint64_t)b, (uint64_t)a); break;
+ case 608: m[s] = (Cell)fop_i2f(a); break;
+ case 609: m[s] = fop_f2i((uint64_t)a); break;
+ case 610: {
+ char buf[400]; int n = fop_format((uint64_t)a, buf), ci;
+ for (ci = 0; ci < n; ci++) micro_putc(buf[ci]);
+ s++;
+ break;
+ }
+ case 618: {
+ char buf[400];
+ int n = fop_format_mode((uint64_t)m[s + 2], a, b, buf), ci;
+ for (ci = 0; ci < n; ci++) micro_putc(buf[ci]);
+ s += 3;
+ break;
+ }
+ case 611:
+ m[s] = inmem(a) ? (Cell)fop_parse_words(m + a, mw - (UCell)a) : 0;
+ break;
+ case 612: m[s] = b_load(a, 1); break;
+ case 613: b_store(a, b, 1); s += 2; break;
+ case 614: m[s] = b_load(a, 2); break;
+ case 615: b_store(a, b, 2); s += 2; break;
+ case 616: m[s] = b_load(a, 4); break;
+ case 617: b_store(a, b, 4); s += 2; break;
+ case 619: { Cell t = s; m[--s] = t; break; }
+ case 620: { /* a native call: there is no native code here, so pop the arguments and answer -1 */
+ int n = 0;
+ Cell sig = (UCell)a < mw - 2 ? m[a + 1] : 0;
+ while (n < 15 && ((sig >> (4 * (n + 1))) & 15)) n++;
+ s += n;
+ m[s] = -1;
+ break; }
+ default:
+ op = (int)(w >> 1);
+ if (op >= XOP0 && op < XOP0 + NENH && enh[op - XOP0].live) s = xrun(&enh[op - XOP0], s);
+ else if (op == 0) p = PC_HALT;
+ else { bad_op = op; p = PC_BAD_OP; }
+ }
+ }
+ if (p == PC_DATA_OVERFLOW) { micro_msg("zy: data stack overflow\n"); return 2; }
+ if (p == PC_RETURN_OVERFLOW) { micro_msg("zy: return stack overflow\n"); return 2; }
+ if (p == PC_PAST_END) { micro_msg("zy: the program counter is past the end of memory\n"); return 2; }
+ if (p == PC_BAD_OP) {
+ char msg[96]; int n = 0, k, neg = bad_op < 0, v = bad_op < 0 ? -bad_op : bad_op;
+ const char *head = "zy: unknown opcode ";
+ while (head[n]) { msg[n] = head[n]; n++; }
+ if (neg) msg[n++] = '-';
+ { char dg[12]; int nd = 0; do { dg[nd++] = (char)('0' + v % 10); v /= 10; } while (v); for (k = nd; k > 0; k--) msg[n++] = dg[k - 1]; }
+ msg[n++] = '\n'; msg[n] = 0;
+ micro_msg(msg);
+ return 2;
+ }
+ return 0;
+}
+
+static int run(int p) { return run_from(p, DS0, RS0); }
+
+/* goes on from a run that suspended for input (MICRO_SUSPENDED), once the port
+ has input for it. the result is as micro_run's */
+int micro_resume(void) { return run_from(sus_p, sus_s, sus_r); }
+
+int micro_run(void)
+{
+ int i;
+ if (!mem_grow_words(memory_initial)) {
+ micro_msg("zy: no memory for the machine\n");
+ return 1;
+ }
+ if ((UCell)GORG + (UCell)GLEN > mw || (UCell)GORG + (UCell)GLEN > (UCell)FBOT) {
+ micro_msg("zy: genome does not fit\n");
+ return 1;
+ }
+ for (i = 0; i < GLEN; i++) m[GORG + i] = genome[i];
+ jknown = 0;
+ return run(GORG);
+}
diff --git a/src/micro/micro_port.h b/src/micro/micro_port.h
new file mode 100644
index 0000000..9f77987
--- /dev/null
+++ b/src/micro/micro_port.h
@@ -0,0 +1,89 @@
+/* host port for the microcore.
+ micro.c calls only these. a bare-metal port is the five functions below:
+ the uart replaces getc/putc, and every syscall returns -1. port_stub.c is
+ the empty shape the freestanding compile check links. src/baremetal/port.c is
+ the one that boots: a 16550 on riscv virt, a PL011 on aarch64 virt.
+ port_linux.c is the hosted one the test suite runs.
+
+ the core does not use stdio, malloc, or the string routines. it does need
+ a 64-bit C compiler (division is the compiler's own runtime, not libc) and
+ a zeroed bss before micro_run. memory starts at MICRO_MW_INIT words of 64
+ bits and grows through micro_mem_alloc when the heap asks for more. */
+#ifndef MICRO_PORT_H
+#define MICRO_PORT_H
+#include <stdint.h>
+#include "memmap.h"
+
+typedef int64_t Cell;
+typedef uint64_t UCell;
+#ifndef MICRO_MW_LIMIT
+#define MICRO_MW_LIMIT 0x7fffffff
+#endif
+/* configure initial words and the growth limit before micro_run. */
+int micro_memory_config(UCell initial, UCell limit);
+/* the machine starts at this many words and grows on demand through micro_mem_alloc.
+ the map's initial heap (HEAP_HI) on a growing map, the whole map (MW) otherwise.
+ there is no fixed ceiling: a run that needs more memory asks the port for it. */
+#ifndef MICRO_MW_INIT
+#if HEAP_GROW
+#define MICRO_MW_INIT HEAP_HI
+#else
+#define MICRO_MW_INIT MW
+#endif
+#endif
+
+/* the machine's memory, in words, from the port. micro_mem_alloc(words) returns a
+ zeroed base with at least words words that stays valid until the next call; the
+ port keeps what it already holds, so a grow keeps the contents. NULL when the port
+ has no more memory. micro_mem_words is the size micro.c has mapped now. */
+Cell *micro_mem_alloc(UCell words);
+UCell micro_mem_words(void);
+
+/* next input byte, 0..255, or -1 at end of input. does not report whether
+ another byte follows; micro_eof does that, and the core asks after each read. */
+int micro_getc(void);
+
+/* 1 when the next micro_getc would return -1, 0 when a byte is waiting.
+ must not consume the byte. called once before the first instruction. */
+int micro_eof(void);
+
+/* streaming input: micro_getc returns MICRO_WAIT when no byte is waiting yet but
+ more may come (micro_eof then says 0). the run then returns MICRO_SUSPENDED,
+ and the port calls micro_resume once it has bytes. the browser and the
+ extension use this for the language server; a port with blocking input never
+ needs it. micro_run and micro_resume return what run returns, MICRO_SUSPENDED
+ included. */
+#define MICRO_WAIT (-2)
+#define MICRO_SUSPENDED 3
+int micro_run(void);
+int micro_resume(void);
+
+/* write the low 8 bits. */
+void micro_putc(int c);
+
+/* a store of op to SYSGATE (ZFP+2; 0xA0002 on the default map). a, b, c are the three words after the gate.
+ mem is the whole machine. return the result, or -1 if op is unsupported.
+ 1 open(path, 0 read | 1 create/truncate) -> fd
+ 2 close(fd)
+ 3 read(fd, buf, n) 4 write(fd, buf, n) one byte per word
+ 5 getenv(name, buf, cap) -> length, or -1
+ 6 time. unix seconds on a hosted port; a hardware counter on bare metal
+ 7 exit(status). does not return
+ 8 a random word, never -1 when the port has a source
+ 9 memory: a = 0 words now, 1 HEAP_LO, 2 HEAP_HI, 3 bytes now, 4 grow to b words
+ 10 bopen(byte_addr, mode) 11 bread(fd, byte_addr, n)
+ 12 bwrite(fd, byte_addr, n) 13 bgetenv(name_byte_addr, dst_byte_addr, cap)
+ ops 1..5 and the names they take are one character per word, 0 terminated.
+ ops 10..13 are packed bytes, 0 terminated, the same layout as a b"..."
+ payload. the bare-metal port keeps files in RAM and answers
+ getenv("BOARD"). the stub returns -1. */
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c);
+
+/* a short diagnostic, usually the stack-overflow lines. not program output.
+ may be a no-op. */
+void micro_msg(const char *s);
+
+/* copy the linked genome in and run from its entry. 0 on halt, 2 on a
+ stack overflow (after a message). */
+int micro_run(void);
+#endif
diff --git a/src/micro/port_linux.c b/src/micro/port_linux.c
new file mode 100644
index 0000000..e6c8847
--- /dev/null
+++ b/src/micro/port_linux.c
@@ -0,0 +1,284 @@
+/* Linux port: stdin/stdout a byte at a time, the syscalls, diagnostics
+ on stderr. same behaviour as io_linux.c plus core.c's hostcall.
+ 1..6 are files, getenv and unix time. 7 exits the process, 8 is a
+ counter, 9 reports the machine size. */
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef _WIN32
+#include <io.h>
+#include <process.h>
+#ifndef O_BINARY
+#define O_BINARY _O_BINARY
+#endif
+#if defined(_MSC_VER)
+#define open _open
+#define close _close
+#define read _read
+#define write _write
+#endif
+#else
+#include <unistd.h>
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "micro_port.h"
+#include "bops.h"
+
+/* the machine's memory: a block that grows by realloc, zero in the new part. the
+ base can move on a grow, which micro.c takes into account (it re-reads m). */
+static Cell *lmem;
+static UCell lcap;
+
+Cell *micro_mem_alloc(UCell words)
+{
+ Cell *n;
+ if (words <= lcap && lmem) return lmem;
+ if (words > ((UCell)-1) / sizeof(Cell)) return NULL;
+ n = realloc(lmem, words * sizeof(Cell));
+ if (!n) return NULL;
+ memset(n + lcap, 0, (words - lcap) * sizeof(Cell));
+ lmem = n;
+ lcap = words;
+ return n;
+}
+
+int micro_getc(void)
+{
+ int c = getchar();
+ return c == EOF ? -1 : c;
+}
+
+int micro_eof(void)
+{
+ int c = getchar();
+ if (c == EOF) return 1;
+ ungetc(c, stdin);
+ return 0;
+}
+
+void micro_putc(int c) { putchar(c & 255); }
+
+void micro_msg(const char *s) { fputs(s, stderr); }
+
+static int zcstr(Cell *mem, Cell addr, char *dst, int max)
+{
+ int i = 0, c;
+ if (max < 1) return -1;
+ if ((UCell)addr >= (UCell)micro_mem_words()) { dst[0] = 0; return -1; }
+ while (i < max - 1 && (UCell)(addr + i) < (UCell)micro_mem_words()) {
+ c = (int)mem[addr + i];
+ if (!c) break;
+ dst[i++] = (char)c;
+ }
+ dst[i] = 0;
+ return i;
+}
+
+static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
+
+/* the command line and the environment, kept for ops 21 to 24: the same
+ contract as src/core/core.c. a program read from stdin has no file name, so
+ argv[0] is <eval>. */
+#ifndef _WIN32
+extern char **environ;
+#endif
+static int zy_argc;
+static char **zy_argv;
+static int zy_envc;
+static char **zy_envp;
+
+static void zy_args(int argc, char **argv)
+{
+ char **e;
+ int n = 0;
+#ifdef _WIN32
+ e = _environ;
+#else
+ e = environ;
+#endif
+ while (e && e[n]) n++;
+ zy_envp = e; zy_envc = n;
+ zy_argv = argv;
+ zy_argc = argc > 0 ? argc : 1;
+}
+
+/* copy entry a of a char ** into vm words, one character per word, terminated
+ like SYS_GETENV. a zero buffer asks for the length only. lead, when given,
+ replaces entry zero. */
+static Cell vecstr(Cell *mem, char **v, int count, Cell a, Cell b, Cell c, const char *lead)
+{
+ char *e;
+ int i = 0;
+ if (a < 0 || a >= (Cell)count) return -1;
+ e = (a == 0 && lead) ? (char *)lead : v[a];
+ if (!e) return -1;
+ if (!b) { while (e[i]) i++; return i; }
+ if (c < 1 || !inmem(b)) return -1;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+}
+
+static Cell host_rand(void)
+{
+ static uint64_t s;
+ if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
+ if (!s) s = 0x9E3779B97F4A7C15ull;
+ s ^= s << 13; s ^= s >> 7; s ^= s << 17;
+ if ((Cell)s == -1) s ^= 0x100;
+ return (Cell)s;
+}
+
+static int memory_words(const char *name, UCell fallback, UCell *words)
+{
+ const char *s = getenv(name);
+ char *end;
+ unsigned long long value;
+ if (!s) { *words = fallback; return 1; }
+ errno = 0;
+ value = strtoull(s, &end, 10);
+ if (errno || end == s || *end || !value || value > 0x7fffffffULL) return 0;
+ *words = (UCell)value;
+ return 1;
+}
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ char path[1024], tmp[512];
+ int n, i, got;
+ switch (op) {
+ case 1:
+ if (zcstr(mem, a, path, 1024) < 0) return -1;
+ if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+ return open(path, O_RDONLY | O_BINARY);
+ case 2:
+ return close((int)a);
+ case 3:
+ if (c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ if (!inmem(b + n)) break;
+ if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
+ if (i <= 0) break;
+ got = (int)read((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ for (i = 0; i < got; i++) mem[b + n + i] = (unsigned char)tmp[i];
+ n += got;
+ }
+ return n;
+ case 4:
+ if (c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ if (!inmem(b + n)) break;
+ if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
+ if (i <= 0) break;
+ for (got = 0; got < i; got++) tmp[got] = (char)mem[b + n + got];
+ got = (int)write((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ n += got;
+ }
+ return n;
+ case 5: {
+ char *e;
+ if (zcstr(mem, a, path, 1024) < 0) return -1;
+ e = getenv(path);
+ if (!e) return -1;
+ if (c < 1 || !inmem(b)) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+ }
+ case 6:
+ return (Cell)time(0);
+ case 7:
+ exit((int)a);
+ case 8:
+ return host_rand();
+ case 9:
+ if (a == 0) return micro_mem_words();
+ if (a == 1) return HEAP_LO;
+ if (a == 2) return HEAP_HI;
+ if (a == 3) return (Cell)micro_mem_words() * 8;
+ return -1;
+ case 10:
+ if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
+ if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+ return open(path, O_RDONLY | O_BINARY);
+ case 11:
+ if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ i = bop_span(b + n, i, micro_mem_words());
+ if (i <= 0) break;
+ got = (int)read((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ for (i = 0; i < got; i++) bop_put1(mem, b + n + i, (unsigned char)tmp[i]);
+ n += got;
+ }
+ return n;
+ case 12:
+ if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ i = bop_span(b + n, i, micro_mem_words());
+ if (i <= 0) break;
+ for (got = 0; got < i; got++) tmp[got] = (char)bop_load(mem, b + n + got, 1);
+ got = (int)write((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ n += got;
+ }
+ return n;
+ case 13: {
+ char *e;
+ if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
+ e = getenv(path);
+ if (!e) return -1;
+ if (c < 1 || !bop_ok(b, 1, micro_mem_words())) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && bop_ok(b + i, 1, micro_mem_words())) { bop_put1(mem, b + i, (unsigned char)e[i]); i++; }
+ if (bop_ok(b + i, 1, micro_mem_words())) bop_put1(mem, b + i, 0);
+ return i;
+ }
+ case 21:
+ return (Cell)zy_argc;
+ case 22:
+ return vecstr(mem, zy_argv, zy_argc, a, b, c, "<eval>");
+ case 23:
+ return (Cell)zy_envc;
+ case 24:
+ return vecstr(mem, zy_envp, zy_envc, a, b, c, 0);
+ default:
+ return -1;
+ }
+}
+
+int main(int argc, char **argv) {
+ UCell initial, limit;
+#ifdef _WIN32
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+#endif
+ zy_args(argc, argv);
+ if (!memory_words("TINYVM_MEMORY_WORDS", MICRO_MW_INIT, &initial) ||
+ !memory_words("TINYVM_MAX_MEMORY_WORDS", MICRO_MW_LIMIT, &limit) ||
+ initial > limit || !micro_memory_config(initial, limit)) {
+ fputs("micro: invalid memory configuration\n", stderr);
+ return 1;
+ }
+ return micro_run();
+}
diff --git a/src/micro/port_stub.c b/src/micro/port_stub.c
new file mode 100644
index 0000000..7bc22a7
--- /dev/null
+++ b/src/micro/port_stub.c
@@ -0,0 +1,59 @@
+/* bare-metal sketch. link this instead of port_linux.c and call micro_run
+ from the reset handler, after bss is cleared.
+
+ point UART_TX at the transmit register. leave micro_getc returning -1 and
+ micro_eof returning 1 when the program arrives in the image and nothing
+ else is typed. micro_syscall returns -1 for every op, which the runtime
+ treats as failure (open fails, time is unavailable). the virt boards do
+ not use this file; their port is src/baremetal/port.c. */
+#include "micro_port.h"
+
+/* example, machine dependent: volatile unsigned char *const UART_TX = (volatile unsigned char *)0x10000000; */
+
+static void uart_putc(unsigned char c)
+{
+ (void)c;
+ /* *UART_TX = c; */
+}
+
+/* the machine's memory on this board: the arena below, which is the board's RAM as
+ memmap.h sizes it. a board's RAM is a fixed amount of hardware, so this is the one
+ place the port has a size; it returns NULL past it. */
+/* physical backing is separate from the vm's configurable growth limit. */
+#ifndef MICRO_PORT_WORDS
+#define MICRO_PORT_WORDS (HEAP_HI > PW ? HEAP_HI : PW)
+#endif
+static Cell arena[MICRO_PORT_WORDS];
+
+Cell *micro_mem_alloc(UCell words)
+{
+ if (words > MICRO_PORT_WORDS) return 0;
+ return arena;
+}
+
+int micro_getc(void) { return -1; }
+int micro_eof(void) { return 1; }
+void micro_putc(int c) { uart_putc((unsigned char)c); }
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ /* no command line and no environment here, so argv[0] is <eval> alone */
+ if (op == 21) return 1;
+ if (op == 23) return 0;
+ if (op == 22) {
+ static const char lead[] = "<eval>";
+ int i;
+ if (a != 0) return -1;
+ if (!b) return 6;
+ for (i = 0; i < 6; i++) mem[b + i] = (unsigned char)lead[i];
+ mem[b + 6] = 0;
+ return 6;
+ }
+ (void)mem; (void)op; (void)a; (void)b; (void)c;
+ return -1;
+}
+
+void micro_msg(const char *s)
+{
+ while (*s) uart_putc((unsigned char)*s++);
+}
diff --git a/src/micro/port_wasm_stream.c b/src/micro/port_wasm_stream.c
new file mode 100644
index 0000000..155cde4
--- /dev/null
+++ b/src/micro/port_wasm_stream.c
@@ -0,0 +1,121 @@
+/* WebAssembly port of the microcore with streaming input, for the language
+ server. the host (the vs code extension) feeds the server's input in pieces
+ and calls wasm_resume after each feed; the server's output comes back one
+ byte at a time through js_putc, and its diagnostics through js_msg. the
+ server suspends when it wants input it does not have yet (MICRO_WAIT in
+ micro_port.h), so a step never blocks the host. build with
+ src/tools/mklspwasm.sh; nothing here needs libc. */
+#include "micro_port.h"
+
+extern void js_putc(int c);
+extern void js_msg(int c);
+
+#define CHUNK 65536
+#define QCAP (8 << 20)
+
+/* the host copies a piece of input here, then calls wasm_feed with its length */
+static unsigned char chunk[CHUNK];
+/* the input the server has not read yet: q[qp..qn) */
+static unsigned char q[QCAP];
+static int qn, qp, closed;
+
+unsigned char *wasm_chunk(void) { return chunk; }
+int wasm_chunk_cap(void) { return (int)sizeof chunk; }
+
+/* appends the first n bytes of chunk to the input. 0 on success, -1 when the
+ input would not fit, which the host must not allow (it drains by running) */
+int wasm_feed(int n)
+{
+ int i;
+ if (n < 0 || n > CHUNK || closed) return -1;
+ if (qn + n > QCAP) { /* move what is unread to the front */
+ int live = qn - qp;
+ if (live + n > QCAP) return -1;
+ for (i = 0; i < live; i++) q[i] = q[qp + i];
+ qp = 0; qn = live;
+ }
+ for (i = 0; i < n; i++) q[qn++] = chunk[i];
+ return 0;
+}
+
+/* no more input will come: the next read past the end is -1 */
+void wasm_close(void) { closed = 1; }
+
+/* the machine's memory: the linear memory above everything the module has in it.
+ the base is fixed when the first machine is made, and a grow only adds pages on
+ the top, so the base never moves. */
+static unsigned char *mbase;
+static UCell mcap; /* bytes of machine memory the module has grown for */
+
+Cell *micro_mem_alloc(UCell words)
+{
+ UCell need, extra, pages;
+ if (words > ((UCell)-1) / sizeof(Cell)) return 0;
+ if (!mbase) {
+ UCell top = (UCell)__builtin_wasm_memory_size(0) * 65536u;
+ mbase = (unsigned char *)top;
+ mcap = 0;
+ }
+ need = words * sizeof(Cell);
+ if (need <= mcap) return (Cell *)mbase;
+ extra = need - mcap;
+ pages = (extra + 65535u) / 65536u;
+ if (__builtin_wasm_memory_grow(0, (int)pages) < 0) return 0;
+ mcap += pages * 65536u; /* wasm zeroes the pages it grows by */
+ return (Cell *)mbase;
+}
+
+int micro_getc(void)
+{
+ if (qp < qn) return q[qp++];
+ return closed ? -1 : MICRO_WAIT;
+}
+int micro_eof(void) { return closed && qp >= qn; }
+void micro_putc(int c) { js_putc(c & 255); }
+void micro_msg(const char *s) { while (*s) js_msg(*s++); }
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ switch (op) {
+ case 9: /* memory: the size of the machine and its heap */
+ if (a == 0) return micro_mem_words();
+ if (a == 1) return HEAP_LO;
+ if (a == 2) return HEAP_HI;
+ if (a == 3) return (Cell)micro_mem_words() * 8;
+ return -1;
+ case 7: /* exit: the run ends */
+ return -1;
+ case 21: /* argc: the server has no command line of its own, so <eval> alone */
+ return 1;
+ case 22: /* argv: entry 0 is <eval>, as in zy(1) */
+ if (a != 0) return -1;
+ if (!b) return 6;
+ if (c < 7 || b + 6 >= micro_mem_words()) return -1;
+ {
+ static const char lead[] = "<eval>";
+ int i;
+ for (i = 0; i < 6; i++) mem[b + i] = (unsigned char)lead[i];
+ mem[b + 6] = 0;
+ }
+ return 6;
+ case 23: /* envc: the host passes no environment */
+ return 0;
+ }
+ return -1;
+}
+
+/* the core multiplies 64 by 64 into 128 bits for the float helpers; wasm has no
+ such instruction and there is no compiler runtime to link, so this is it */
+void __multi3(unsigned long long *r, unsigned long long al, unsigned long long ah, unsigned long long bl, unsigned long long bh)
+{
+ unsigned long long a0 = al & 0xffffffffu, a1 = al >> 32, b0 = bl & 0xffffffffu, b1 = bl >> 32;
+ unsigned long long p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1;
+ unsigned long long mid = (p00 >> 32) + (p01 & 0xffffffffu) + (p10 & 0xffffffffu);
+ r[0] = (p00 & 0xffffffffu) | (mid << 32);
+ r[1] = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32) + al * bh + ah * bl;
+}
+
+/* loads the server and runs it until it halts or waits for input (3) */
+int wasm_start(void) { return micro_run(); }
+/* goes on after the host fed input: as wasm_start, halt (0), error (2) or wait (3) */
+int wasm_resume(void) { return micro_resume(); }
diff --git a/src/micro/port_win.c b/src/micro/port_win.c
new file mode 100644
index 0000000..8e3ef74
--- /dev/null
+++ b/src/micro/port_win.c
@@ -0,0 +1,268 @@
+/* Windows port for the microcore: stdin/stdout a byte at a time, the syscalls,
+ diagnostics on stderr. same behaviour as io_win.c plus core.c's hostcall.
+ 1..6 are files, getenv and unix time. 7 exits the process, 8 is a
+ counter, 9 reports the machine size. */
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <io.h>
+#include <process.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "micro_port.h"
+#include "bops.h"
+
+#ifndef O_BINARY
+#define O_BINARY _O_BINARY
+#endif
+#if defined(_MSC_VER)
+#define open _open
+#define close _close
+#define read _read
+#define write _write
+#endif
+
+/* the machine's memory: a block that grows by realloc, zero in the new part. the
+ base can move on a grow, which micro.c takes into account (it re-reads m). */
+static Cell *lmem;
+static UCell lcap;
+
+Cell *micro_mem_alloc(UCell words)
+{
+ Cell *n;
+ if (words <= lcap && lmem) return lmem;
+ if (words > ((UCell)-1) / sizeof(Cell)) return NULL;
+ n = realloc(lmem, words * sizeof(Cell));
+ if (!n) return NULL;
+ memset(n + lcap, 0, (words - lcap) * sizeof(Cell));
+ lmem = n;
+ lcap = words;
+ return n;
+}
+
+int micro_getc(void)
+{
+ int c = _fgetc_nolock(stdin);
+ return c == EOF ? -1 : c;
+}
+
+int micro_eof(void)
+{
+ int c = _fgetc_nolock(stdin);
+ if (c == EOF) return 1;
+ _ungetc_nolock(c, stdin);
+ return 0;
+}
+
+void micro_putc(int c) { _fputc_nolock(c & 255, stdout); }
+
+void micro_msg(const char *s) { fputs(s, stderr); }
+
+static int zcstr(Cell *mem, Cell addr, char *dst, int max)
+{
+ int i = 0, c;
+ if (max < 1) return -1;
+ if ((UCell)addr >= (UCell)micro_mem_words()) { dst[0] = 0; return -1; }
+ while (i < max - 1 && (UCell)(addr + i) < (UCell)micro_mem_words()) {
+ c = (int)mem[addr + i];
+ if (!c) break;
+ dst[i++] = (char)c;
+ }
+ dst[i] = 0;
+ return i;
+}
+
+static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
+
+/* the command line and the environment, kept for ops 21 to 24: the same
+ contract as src/core/core.c. a program read from stdin has no file name, so
+ argv[0] is <eval>. */
+static int zy_argc;
+static char **zy_argv;
+static int zy_envc;
+static char **zy_envp;
+
+static void zy_args(int argc, char **argv)
+{
+ char **e = _environ;
+ int n = 0;
+ while (e && e[n]) n++;
+ zy_envp = e; zy_envc = n;
+ zy_argv = argv;
+ zy_argc = argc > 0 ? argc : 1;
+}
+
+/* copy entry a of a char ** into vm words, one character per word, terminated
+ like SYS_GETENV. a zero buffer asks for the length only. lead, when given,
+ replaces entry zero. */
+static Cell vecstr(Cell *mem, char **v, int count, Cell a, Cell b, Cell c, const char *lead)
+{
+ char *e;
+ int i = 0;
+ if (a < 0 || a >= (Cell)count) return -1;
+ e = (a == 0 && lead) ? (char *)lead : v[a];
+ if (!e) return -1;
+ if (!b) { while (e[i]) i++; return i; }
+ if (c < 1 || !inmem(b)) return -1;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+}
+
+static Cell host_rand(void)
+{
+ static uint64_t s;
+ if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
+ if (!s) s = 0x9E3779B97F4A7C15ull;
+ s ^= s << 13; s ^= s >> 7; s ^= s << 17;
+ if ((Cell)s == -1) s ^= 0x100;
+ return (Cell)s;
+}
+
+static int memory_words(const char *name, UCell fallback, UCell *words)
+{
+ const char *s = getenv(name);
+ char *end;
+ unsigned long long value;
+ if (!s) { *words = fallback; return 1; }
+ errno = 0;
+ value = strtoull(s, &end, 10);
+ if (errno || end == s || *end || !value || value > 0x7fffffffULL) return 0;
+ *words = (UCell)value;
+ return 1;
+}
+
+Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
+{
+ char path[1024], tmp[512];
+ int n, i, got;
+ switch (op) {
+ case 1:
+ if (zcstr(mem, a, path, 1024) < 0) return -1;
+ if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+ return open(path, O_RDONLY | O_BINARY);
+ case 2:
+ return close((int)a);
+ case 3:
+ if (c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ if (!inmem(b + n)) break;
+ if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
+ if (i <= 0) break;
+ got = (int)read((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ for (i = 0; i < got; i++) mem[b + n + i] = (unsigned char)tmp[i];
+ n += got;
+ }
+ return n;
+ case 4:
+ if (c < 0 || !inmem(b)) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ if (!inmem(b + n)) break;
+ if (!inmem(b + n + i - 1)) i = (int)(micro_mem_words() - (b + n));
+ if (i <= 0) break;
+ for (got = 0; got < i; got++) tmp[got] = (char)mem[b + n + got];
+ got = (int)write((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ n += got;
+ }
+ return n;
+ case 5: {
+ char *e;
+ if (zcstr(mem, a, path, 1024) < 0) return -1;
+ e = getenv(path);
+ if (!e) return -1;
+ if (c < 1 || !inmem(b)) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && inmem(b + i)) { mem[b + i] = (unsigned char)e[i]; i++; }
+ if (inmem(b + i)) mem[b + i] = 0;
+ return i;
+ }
+ case 6:
+ return (Cell)time(0);
+ case 7:
+ exit((int)a);
+ case 8:
+ return host_rand();
+ case 9:
+ if (a == 0) return micro_mem_words();
+ if (a == 1) return HEAP_LO;
+ if (a == 2) return HEAP_HI;
+ if (a == 3) return (Cell)micro_mem_words() * 8;
+ return -1;
+ case 10:
+ if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
+ if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+ return open(path, O_RDONLY | O_BINARY);
+ case 11:
+ if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ i = bop_span(b + n, i, micro_mem_words());
+ if (i <= 0) break;
+ got = (int)read((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ for (i = 0; i < got; i++) bop_put1(mem, b + n + i, (unsigned char)tmp[i]);
+ n += got;
+ }
+ return n;
+ case 12:
+ if (c < 0 || (c > 0 && !bop_ok(b, 1, micro_mem_words()))) return -1;
+ n = 0;
+ while (n < c) {
+ i = (int)(c - n);
+ if (i > 512) i = 512;
+ i = bop_span(b + n, i, micro_mem_words());
+ if (i <= 0) break;
+ for (got = 0; got < i; got++) tmp[got] = (char)bop_load(mem, b + n + got, 1);
+ got = (int)write((int)a, tmp, (size_t)i);
+ if (got <= 0) break;
+ n += got;
+ }
+ return n;
+ case 13: {
+ char *e;
+ if (bop_cstr(mem, a, path, 1024, micro_mem_words()) < 0) return -1;
+ e = getenv(path);
+ if (!e) return -1;
+ if (c < 1 || !bop_ok(b, 1, micro_mem_words())) return -1;
+ i = 0;
+ while (e[i] && i < c - 1 && bop_ok(b + i, 1, micro_mem_words())) { bop_put1(mem, b + i, (unsigned char)e[i]); i++; }
+ if (bop_ok(b + i, 1, micro_mem_words())) bop_put1(mem, b + i, 0);
+ return i;
+ }
+ case 21:
+ return (Cell)zy_argc;
+ case 22:
+ return vecstr(mem, zy_argv, zy_argc, a, b, c, "<eval>");
+ case 23:
+ return (Cell)zy_envc;
+ case 24:
+ return vecstr(mem, zy_envp, zy_envc, a, b, c, 0);
+ default:
+ return -1;
+ }
+}
+
+int main(int argc, char **argv) {
+ UCell initial, limit;
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+ zy_args(argc, argv);
+ if (!memory_words("TINYVM_MEMORY_WORDS", MICRO_MW_INIT, &initial) ||
+ !memory_words("TINYVM_MAX_MEMORY_WORDS", MICRO_MW_LIMIT, &limit) ||
+ initial > limit || !micro_memory_config(initial, limit)) {
+ fputs("micro: invalid memory configuration\n", stderr);
+ return 1;
+ }
+ return micro_run();
+}