From ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Mon, 12 Oct 2026 08:46:23 +0330 Subject: a --- src/micro/micro.c | 643 +++++++++++++++++++++++++++++++++++++++++++ src/micro/micro_port.h | 89 ++++++ src/micro/port_linux.c | 284 +++++++++++++++++++ src/micro/port_stub.c | 59 ++++ src/micro/port_wasm_stream.c | 121 ++++++++ src/micro/port_win.c | 268 ++++++++++++++++++ 6 files changed, 1464 insertions(+) create mode 100644 src/micro/micro.c create mode 100644 src/micro/micro_port.h create mode 100644 src/micro/port_linux.c create mode 100644 src/micro/port_stub.c create mode 100644 src/micro/port_wasm_stream.c create mode 100644 src/micro/port_win.c (limited to 'src/micro') 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 +#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 +#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 +#include +#include +#ifdef _WIN32 +#include +#include +#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 +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#endif +#include +#include +#include +#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 . */ +#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, ""); + 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 alone */ + if (op == 21) return 1; + if (op == 23) return 0; + if (op == 22) { + static const char lead[] = ""; + 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 alone */ + return 1; + case 22: /* argv: entry 0 is , 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[] = ""; + 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 +#include +#include +#include +#include +#include +#include +#include +#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 . */ +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, ""); + 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(); +} -- cgit v1.2.3