aboutsummaryrefslogtreecommitdiff
path: root/src/micro
diff options
context:
space:
mode:
Diffstat (limited to 'src/micro')
-rw-r--r--src/micro/micro.c172
-rw-r--r--src/micro/port_linux.c167
-rw-r--r--src/micro/port_wasm_stream.c10
3 files changed, 314 insertions, 35 deletions
diff --git a/src/micro/micro.c b/src/micro/micro.c
index 4542bd8..528382a 100644
--- a/src/micro/micro.c
+++ b/src/micro/micro.c
@@ -19,6 +19,17 @@
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 ZY_THREADS
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
#ifdef USE_LSP_GENOME
#include "lsp_genome.h"
#elif defined(GENOME_H)
@@ -68,22 +79,22 @@ typedef struct {
/* 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;
+static VM_TLS Cell *m;
+static VM_TLS UCell mw;
+static VM_TLS 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 */
+static VM_TLS Xop xops[XPOOL];
+static VM_TLS int xuse;
+static VM_TLS Enh enh[NENH];
+static VM_TLS Xop build[MAXOPS];
+static VM_TLS int bn, bnv, berr;
+static VM_TLS Cell xv[MAXV];
+static VM_TLS int jflag;
+static VM_TLS 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; }
@@ -109,7 +120,7 @@ static void b_store(Cell addr, Cell val, int n)
#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;
+static VM_TLS 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. */
@@ -482,13 +493,16 @@ static int xrun(Enh *e, int 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 VM_TLS int sus_p, sus_s, sus_r;
+static VM_TLS int final_s;
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp);
+static int context_cancelled(void);
static int run_from(int p, int s, int r)
{
int op;
Cell w, a, b;
- while ((UCell)p < mw) {
+ while ((UCell)p < mw && !context_cancelled()) {
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) {
@@ -517,7 +531,16 @@ static int run_from(int p, int s, int r)
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]);
+ } else if (b == 41) {
+ char text[400]; int i, n = fop_format_mode((uint64_t)m[a + 1], 0, 17, text);
+ Cell dst = m[a + 2], cap = m[a + 3];
+ if (dst < 0 || cap <= n || (UCell)dst > mw || (UCell)cap > mw - (UCell)dst) result = -1;
+ else { for (i = 0; i < n; i++) m[dst + i] = (unsigned char)text[i]; m[dst + n] = 0; result = n; }
+ } else if (b == 42) {
+ /* poll: a streaming port suspends once, so its host can deliver input that is waiting */
+ result = micro_syscall(m, b, 0, 0, 0);
+ if (result == MICRO_WAIT) { sus_p = p - 1; sus_s = s; sus_r = r; return MICRO_SUSPENDED; }
+ } else result = context_call(b, m[a + 1], m[a + 2], m[a + 3], s + 2, r);
m[a] = result;
s += 2;
break;
@@ -604,6 +627,7 @@ static int run_from(int p, int s, int r)
else { bad_op = op; p = PC_BAD_OP; }
}
}
+ final_s = s;
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; }
@@ -620,6 +644,124 @@ static int run_from(int p, int s, int r)
return 0;
}
+/* hosted contexts own their memory, input buffer and channel endpoints. */
+#ifdef ZY_THREADS
+extern size_t micro_context_port_size(void);
+extern void micro_context_port_save(void *);
+extern void micro_context_port_restore(const void *);
+extern void micro_context_port_free(void);
+#define CONTEXT_HANDLE_LO 0x40000000
+typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel;
+typedef struct VmContext {
+ ContextChannel parent_channel, worker_channel;
+ pthread_t thread;
+ struct VmContext *next;
+ atomic_int stop;
+ Cell token, entry, stack;
+ UCell words, limit, initial;
+ int sp, rp, exit_code, requested_exit, enhancement_count, input_flag, input_known;
+ Cell *memory;
+ void *port;
+ Xop operations[XPOOL];
+ Enh enhancements[NENH];
+} VmContext;
+static VM_TLS VmContext *context_current, *context_children;
+static VM_TLS ContextChannel *context_channels;
+static atomic_llong context_sequence;
+static atomic_int context_records_live, context_memory_live;
+static Cell context_join(Cell token, int cancel);
+static int context_cancelled(void) { return context_current && atomic_load_explicit(&context_current->stop, memory_order_acquire); }
+static int context_descriptor(Cell handle) {
+ if (handle < CONTEXT_HANDLE_LO) return handle >= 0 && handle <= 0x7fffffff ? (int)handle : -1;
+ for (ContextChannel *channel = context_channels; channel; channel = channel->next) if (channel->handle == handle) return channel->fd;
+ return -1;
+}
+static int context_close(Cell handle) {
+ ContextChannel **link = &context_channels;
+ while (*link && (*link)->handle != handle) link = &(*link)->next;
+ if (!*link) return -1;
+ ContextChannel *channel = *link; *link = channel->next;
+ if (channel->fd < 0) return -1;
+ int result = close(channel->fd); channel->fd = -1; return result;
+}
+static void context_snapshot_free(VmContext *context) {
+ free(context->port);
+ if (context->memory) { free(context->memory); atomic_fetch_sub(&context_memory_live, 1); }
+}
+static void *context_worker(void *opaque) {
+ VmContext *context = opaque;
+ context_current = context; context->worker_channel.next = context_channels; context_channels = &context->worker_channel;
+ micro_context_port_restore(context->port); free(context->port); context->port = 0;
+ memory_limit = context->limit; memory_initial = context->initial;
+ if (!mem_grow_words(context->words)) { context->exit_code = -1; context_close(context->worker_channel.handle); return 0; }
+ memcpy(m, context->memory, (size_t)context->words * sizeof(Cell)); free(context->memory); context->memory = 0;
+ stack_control = context->stack; jflag = context->input_flag; jknown = context->input_known;
+ xuse = context->enhancement_count; memcpy(xops, context->operations, sizeof xops); memcpy(enh, context->enhancements, sizeof enh);
+ int sp = context->sp, rp = context->rp; Cell entry = context->entry;
+ if (entry >= HEAP_LO && m[entry - 3] == (entry ^ 0x2B7E1516)) { m[--sp] = entry + 3; entry = m[entry]; }
+ m[rp++] = PC_HALT;
+ int result = run_from((int)entry, sp, rp);
+ if (!context->requested_exit) context->exit_code = context_cancelled() ? 0 : result ? -1 : inmem(final_s) ? (int)m[final_s] : 0;
+ context_close(context->worker_channel.handle);
+ while (context_children) context_join(context_children->token, 1);
+ micro_context_port_free(); m = 0; atomic_fetch_sub(&context_memory_live, 1); context_current = 0; return 0;
+}
+static Cell context_start(Cell entry, Cell channel, int sp, int rp) {
+ if (!inmem(entry) || !inmem(channel) || (entry >= HEAP_LO && (!inmem(entry - 3) || m[entry - 3] != (entry ^ 0x2B7E1516)))) return -1;
+ int fd[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ VmContext *context = calloc(1, sizeof *context); if (!context) { close(fd[0]); close(fd[1]); return -1; }
+ context->entry = entry; context->sp = sp; context->rp = rp; context->stack = stack_control;
+ context->words = mw; context->limit = memory_limit; context->initial = memory_initial;
+ context->input_flag = jflag; context->input_known = jknown; context->enhancement_count = xuse;
+ context->token = atomic_fetch_add(&context_sequence, 1) + 1;
+ context->parent_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1;
+ context->worker_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1;
+ context->parent_channel.fd = fd[0]; context->worker_channel.fd = fd[1]; atomic_init(&context->stop, 0);
+ context->port = malloc(micro_context_port_size()); context->memory = malloc((size_t)mw * sizeof(Cell));
+ if (context->memory) atomic_fetch_add(&context_memory_live, 1);
+ if (!context->port || !context->memory) goto failed;
+ micro_context_port_save(context->port); memcpy(context->memory, m, (size_t)mw * sizeof(Cell)); context->memory[channel] = context->worker_channel.handle;
+ memcpy(context->operations, xops, sizeof xops); memcpy(context->enhancements, enh, sizeof enh);
+ pthread_attr_t attributes; pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 16u << 20);
+ int result = pthread_create(&context->thread, &attributes, context_worker, context); pthread_attr_destroy(&attributes);
+ if (result) goto failed;
+ context->parent_channel.next = context_channels; context_channels = &context->parent_channel;
+ atomic_fetch_add(&context_records_live, 1); m[channel] = context->parent_channel.handle;
+ context->next = context_children; context_children = context; return context->token;
+failed:
+ context_snapshot_free(context); free(context); close(fd[0]); close(fd[1]); return -1;
+}
+static Cell context_join(Cell token, int cancel) {
+ VmContext **link = &context_children;
+ while (*link && (*link)->token != token) link = &(*link)->next;
+ if (!*link) return -1;
+ VmContext *context = *link;
+ if (cancel) { atomic_store_explicit(&context->stop, 1, memory_order_release); if (context->parent_channel.fd >= 0) shutdown(context->parent_channel.fd, SHUT_RDWR); }
+ if (pthread_join(context->thread, 0)) return -1;
+ if (context->parent_channel.fd >= 0) context_close(context->parent_channel.handle);
+ int result = context->exit_code; *link = context->next; context_snapshot_free(context); free(context);
+ atomic_fetch_sub(&context_records_live, 1); return result;
+}
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp) {
+ switch (op) {
+ case 51: return context_start(a, b, sp, rp);
+ case 52: return context_join(a, b != 0);
+ case 53: return getpid();
+ case 54: return (Cell)(uintptr_t)pthread_self();
+ case 55: return a == 0 ? atomic_load(&context_records_live) : a == 1 ? 0 : a == 2 ? atomic_load(&context_memory_live) : -1;
+ case 7:
+ if (context_current) { context_current->exit_code = (int)a; context_current->requested_exit = 1; atomic_store_explicit(&context_current->stop, 1, memory_order_release); return 0; }
+ break;
+ case 2: if (a >= CONTEXT_HANDLE_LO) return context_close(a); break;
+ case 44: case 45: case 46: a = context_descriptor(a); if (a < 0) return -1; break;
+ }
+ return micro_syscall(m, op, a, b, c);
+}
+#else
+static int context_cancelled(void) { return 0; }
+static Cell context_call(Cell op, Cell a, Cell b, Cell c, int sp, int rp) { return micro_syscall(m, op, a, b, c); }
+#endif
+
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
diff --git a/src/micro/port_linux.c b/src/micro/port_linux.c
index e6c8847..9331a0b 100644
--- a/src/micro/port_linux.c
+++ b/src/micro/port_linux.c
@@ -19,6 +19,10 @@
#endif
#else
#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <signal.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
@@ -28,11 +32,16 @@
#include <time.h>
#include "micro_port.h"
#include "bops.h"
+#ifdef ZY_THREADS
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#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;
+static VM_TLS Cell *lmem;
+static VM_TLS UCell lcap;
Cell *micro_mem_alloc(UCell words)
{
@@ -47,19 +56,24 @@ Cell *micro_mem_alloc(UCell 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;
+#if defined(ZY_THREADS)
+static VM_TLS unsigned char input_bytes[65536];
+static VM_TLS int input_position, input_count, input_ended;
+static int input_fill(int wait) {
+ if (input_position < input_count) return 1;
+ if (input_ended) return 0;
+ if (!wait) { struct pollfd descriptor = {0, POLLIN, 0}; if (poll(&descriptor, 1, 0) <= 0) return 0; }
+ else fflush(stdout);
+ int n = (int)read(0, input_bytes, sizeof input_bytes);
+ if (n <= 0) { input_ended = 1; return 0; }
+ input_position = 0; input_count = n; return 1;
}
+int micro_getc(void) { return input_fill(1) ? input_bytes[input_position++] : -1; }
+int micro_eof(void) { input_fill(0); return input_ended; }
+#else
+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; }
+#endif
void micro_putc(int c) { putchar(c & 255); }
@@ -87,10 +101,10 @@ static int inmem(Cell a) { return (UCell)a < (UCell)micro_mem_words(); }
#ifndef _WIN32
extern char **environ;
#endif
-static int zy_argc;
-static char **zy_argv;
-static int zy_envc;
-static char **zy_envp;
+static VM_TLS int zy_argc;
+static VM_TLS char **zy_argv;
+static VM_TLS int zy_envc;
+static VM_TLS char **zy_envp;
static void zy_args(int argc, char **argv)
{
@@ -126,7 +140,7 @@ static Cell vecstr(Cell *mem, char **v, int count, Cell a, Cell b, Cell c, const
static Cell host_rand(void)
{
- static uint64_t s;
+ static VM_TLS uint64_t s;
if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull;
if (!s) s = 0x9E3779B97F4A7C15ull;
s ^= s << 13; s ^= s >> 7; s ^= s << 17;
@@ -152,6 +166,84 @@ 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 43: { /* clone an isolated execution context with a bidirectional channel */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int fd[2]; pid_t child;
+ if (!inmem(a) || socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1;
+ fflush(NULL); child = fork();
+ if (child < 0) { close(fd[0]); close(fd[1]); return -1; }
+ if (!child) { close(fd[0]); mem[a] = fd[1]; return 0; }
+ close(fd[1]); mem[a] = fd[0]; return child;
+#else
+ return -1;
+#endif
+ }
+ case 44: case 45: { /* write/read complete word buffers on a context channel */
+#if !defined(_WIN32) && !defined(__wasm__)
+ size_t done = 0, length; ssize_t got; unsigned char *buffer;
+ if (b < 0 || c < 0 || c > (Cell)micro_mem_words() - b) return -1;
+ buffer = (unsigned char *)(mem + b); length = (size_t)c * sizeof(Cell);
+ while (done < length) {
+ if (op == 44) {
+#ifdef __APPLE__
+ int enabled = 1; setsockopt((int)a, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof enabled);
+ #ifdef MSG_NOSIGNAL
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#else
+ got = send((int)a, buffer + done, length - done, 0);
+#endif
+#else
+ got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL);
+#endif
+ } else got = read((int)a, buffer + done, length - done);
+ if (got < 0 && errno == EINTR) continue;
+ if (got <= 0) return -1;
+ done += (size_t)got;
+ }
+ return c;
+#else
+ return -1;
+#endif
+ }
+ case 46: { /* channel readiness, with a millisecond timeout */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct pollfd fd; int ready; fd.fd = (int)a; fd.events = POLLIN; fd.revents = 0;
+ do { ready = poll(&fd, 1, (int)b); } while (ready < 0 && errno == EINTR);
+ return ready > 0 ? !!(fd.revents & (POLLIN | POLLHUP)) : ready;
+#else
+ return -1;
+#endif
+ }
+ case 47: case 49: { /* join a child context, optionally terminating it first */
+#if !defined(_WIN32) && !defined(__wasm__)
+ int status; pid_t joined;
+ if (a <= 0) return -1;
+ if (op == 49) kill((pid_t)a, SIGTERM);
+ do { joined = waitpid((pid_t)a, &status, 0); } while (joined < 0 && errno == EINTR);
+ return joined < 0 ? -1 : WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+#else
+ return -1;
+#endif
+ }
+ case 50: { /* monotonic milliseconds for elapsed-time measurements */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return -1;
+ return (Cell)now.tv_sec * 1000 + now.tv_nsec / 1000000;
+#else
+ return -1;
+#endif
+ }
+ case 48: { /* sleep for a bounded millisecond duration */
+#if !defined(_WIN32) && !defined(__wasm__)
+ struct timespec duration;
+ if (a < 0 || a > 86400000) return -1;
+ duration.tv_sec = (time_t)(a / 1000); duration.tv_nsec = (long)(a % 1000) * 1000000;
+ while (nanosleep(&duration, &duration) < 0) if (errno != EINTR) return -1;
+ return 0;
+#else
+ return -1;
+#endif
+ }
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);
@@ -269,6 +361,21 @@ Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
int main(int argc, char **argv) {
UCell initial, limit;
+ if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) {
+ fputs("usage: zym [program arguments] < input\n"
+ "runs the embedded image with the portable vm; compiler images read source from stdin.\n"
+ "input after a backtick is program input; run-only images read program input directly.\n"
+ "program arguments are available through std.argv; stdin has the name <eval>.\n"
+ "options:\n --help, -h show this help and exit\n"
+ "environment:\n"
+ " TINYVM_MEMORY_WORDS initial vm memory in words\n"
+ " TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n", stdout);
+ return 0;
+ }
+ if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') {
+ fprintf(stderr, "micro: unknown option %s; use --help for usage\n", argv[1]);
+ return 2;
+ }
#ifdef _WIN32
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
@@ -282,3 +389,25 @@ int main(int argc, char **argv) {
}
return micro_run();
}
+
+#ifdef ZY_THREADS
+struct PortSnapshot {
+ unsigned char bytes[65536];
+ int position, count, ended, argc, envc;
+ char **argv, **envp;
+};
+size_t micro_context_port_size(void) { return sizeof(struct PortSnapshot); }
+void micro_context_port_save(void *destination) {
+ struct PortSnapshot *snapshot = destination;
+ memcpy(snapshot->bytes, input_bytes, sizeof input_bytes);
+ snapshot->position = input_position; snapshot->count = input_count; snapshot->ended = input_ended;
+ snapshot->argc = zy_argc; snapshot->argv = zy_argv; snapshot->envc = zy_envc; snapshot->envp = zy_envp;
+}
+void micro_context_port_restore(const void *source) {
+ const struct PortSnapshot *snapshot = source;
+ memcpy(input_bytes, snapshot->bytes, sizeof input_bytes);
+ input_position = snapshot->position; input_count = snapshot->count; input_ended = snapshot->ended;
+ zy_argc = snapshot->argc; zy_argv = snapshot->argv; zy_envc = snapshot->envc; zy_envp = snapshot->envp;
+}
+void micro_context_port_free(void) { free(lmem); lmem = 0; lcap = 0; }
+#endif
diff --git a/src/micro/port_wasm_stream.c b/src/micro/port_wasm_stream.c
index 155cde4..d96ceab 100644
--- a/src/micro/port_wasm_stream.c
+++ b/src/micro/port_wasm_stream.c
@@ -17,7 +17,7 @@ extern void js_msg(int c);
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;
+static int qn, qp, closed, polled;
unsigned char *wasm_chunk(void) { return chunk; }
int wasm_chunk_cap(void) { return (int)sizeof chunk; }
@@ -38,6 +38,10 @@ int wasm_feed(int n)
return 0;
}
+/* 1 while the server is suspended by a poll rather than by a read: the host should resume it
+ after the event loop has run, whether or not input came */
+int wasm_polling(void) { return polled; }
+
/* no more input will come: the next read past the end is -1 */
void wasm_close(void) { closed = 1; }
@@ -83,6 +87,10 @@ Cell micro_syscall(Cell *mem, Cell op, Cell a, Cell b, Cell c)
if (a == 2) return HEAP_HI;
if (a == 3) return (Cell)micro_mem_words() * 8;
return -1;
+ case 42: /* poll: suspend once so the host can feed what has arrived, then say if input is waiting */
+ if (!polled) { polled = 1; return MICRO_WAIT; }
+ polled = 0;
+ return qp < qn;
case 7: /* exit: the run ends */
return -1;
case 21: /* argc: the server has no command line of its own, so <eval> alone */