diff options
| author | 2026-10-14 08:29:46 +0330 | |
|---|---|---|
| committer | 2026-10-14 08:29:46 +0330 | |
| commit | 1c53304d03414890af0da7432d1aea87e754a14a (patch) | |
| tree | 97190d86cf548c9bc63f68c86a4d2a4f09809713 /src/micro/micro.c | |
| parent | a (diff) | |
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<I,O>, 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
Diffstat (limited to 'src/micro/micro.c')
| -rw-r--r-- | src/micro/micro.c | 172 |
1 files changed, 157 insertions, 15 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 |
