From 1c53304d03414890af0da7432d1aea87e754a14a Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Wed, 14 Oct 2026 08:29:46 +0330 Subject: feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests --- src/micro/micro.c | 172 +++++++++++++++++++++++++++++++++++++++---- src/micro/port_linux.c | 167 ++++++++++++++++++++++++++++++++++++----- src/micro/port_wasm_stream.c | 10 ++- 3 files changed, 314 insertions(+), 35 deletions(-) (limited to 'src/micro') 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 +#ifdef ZY_THREADS +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include #ifndef O_BINARY #define O_BINARY 0 #endif @@ -28,11 +32,16 @@ #include #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 .\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 alone */ -- cgit v1.2.3