aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 07:55:08 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 07:55:08 +0000
commitd8da21a406f449d277c42b91d4c9bf8b20bd0dcc (patch)
tree79a4dbf6839eaee19aa626f1aa3981d5b0c19377 /README.md
parentbuild: embed images with #embed, keep everything generated in build/ (diff)
embed: tests, editor support and docs; js vm file access; test fixes
embed declarations get accept and reject fixtures, a run-only image check through zc0, an lsp keyword hover and the grammar keyword. errors point at the declaration and still declare the name, so its uses are not reported again. zc0 tracks byte-string globals and lays embedded arrays out one word per element, the way its own indexing reads them. the command-line javascript vm reaches node's file system for the file syscalls, so include and embed work there; other embedders opt in. the web build is self-contained in build/web with its examples. README, BORROW and BOOTSTRAP-OWNERSHIP describe the new build and ownership rules, and ONBOARD.md is a reading and verification order for the code. the suite passes the map header to mklayout, the wasm fuzz program uses the include format, and lsp drivers scale their deadlines on hosts without the arm64 jit (ZC_TEST_TIMEOUT_SCALE). LLVM_CONFIG selects an llvm with headers for the llvm section.
Diffstat (limited to 'README.md')
-rw-r--r--README.md75
1 files changed, 49 insertions, 26 deletions
diff --git a/README.md b/README.md
index e782167..2a7a073 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ this folder has the latest code. `docs/LEARN.md` is a tour of the language. `doc
## files
-`src/core/core.c` is the native core. it contains the arm64 jit, the llvm backend (behind `-DZYLLVM`), the portable interpreter, the enhancement unit (recipe parser, vm-code lifter, optimizer and lowering), and the o3 region compiler. `src/runtime/rt.zc` provides i/o, native division enhancements, the heap, and the collector; `src/compiler/compiler.zc` is the compiler. both are written in zc and begin with an overview. together they make up the genome. `src/tools/zc0.c` is stage 0 of the bootstrap, a c compiler for the subset of zc used by `src/compiler/compiler.zc`. `src/tools/build.zc` has the resident compiler compile its own source into a fresh image, and `src/tools/mkgenome.c` turns that image into the generated `build/core/genome.h`. `examples/` contains small programs (hello, factorial, echo, self-patching code, an owning list, a repl, a typed program, and an enhancement demo) and larger proofs of concept:
+`src/core/core.c` is the native core. it contains the arm64 jit, the llvm backend (behind `-DZYLLVM`), the portable interpreter, the enhancement unit (recipe parser, vm-code lifter, optimizer and lowering), and the o3 region compiler. `src/runtime/rt.zc` provides i/o, native division enhancements, the heap, and the collector; `src/compiler/compiler.zc` is the compiler. both are written in zc and begin with an overview. together they make up the genome. `src/tools/zc0.c` is stage 0 of the bootstrap, a c compiler for the subset of zc used by `src/compiler/compiler.zc`. `src/tools/build.zc` has the resident compiler compile its own source into a fresh image, and `src/tools/mkgenome.c` checks that image against the memory map before it becomes `build/core/genome.bin`. an image is a file of little-endian 64-bit words, and every core takes its genome in at compile time with c23 `#embed` (`src/core/genome.h`). `examples/` contains small programs (hello, factorial, echo, self-patching code, an owning list, a repl, a typed program, and an enhancement demo) and larger proofs of concept:
- `mandel.zc`: the mandelbrot set in 16.16 fixed point, with the fixed-point multiply wired in as a custom opcode.
- `bf.zc`: a brainfuck compiler. it turns brainfuck into zc source, has the resident compiler compile it, and runs it (native code on the jit). it takes a program after a backtick, or runs hello world.
@@ -25,11 +25,11 @@ this folder has the latest code. `docs/LEARN.md` is a tour of the language. `doc
## build and run
-run these commands from the repository root. `make` writes executables to `bin/` and temporary builds to `build/`:
+run these commands from the repository root. everything built or generated goes under `build/`: executables in `build/bin/`, the default memory map in `build/core/map/`, the compiler genome in `build/core/genome.bin`, and the javascript, web and editor builds beside them. the tree holds sources only, `make clean` removes `build/`, and every target builds from an empty `build/`:
```sh
make
-bin/zy < examples/hello.zc
+build/bin/zy < examples/hello.zc
make test JOBS=4
make genome
make web
@@ -37,15 +37,15 @@ make js
make run PROG=examples/hello.zc
```
-`make genome` bootstraps the compiler, compares two self-compiled images, and updates `build/core/genome.h` only after they agree. rebuild the native, javascript, and web runtimes after changing the genome. `make check` runs the smaller suite while iterating.
+the cores embed their genome with c23 `#embed`, so the c compiler has to be clang 19 or gcc 15 or later. `src/tools/findcc.sh` finds one (`CC` set on the command line or in the environment is used as given, and must support it too). `make genome` bootstraps the compiler, compares two self-compiled images, and writes `build/core/genome.bin` only after they agree; `make` reruns it whenever a source of the genome changes, and the cores that embed it are rebuilt after it. `make check` runs the smaller suite while iterating. `ONBOARD.md` is a reading and verification order for getting to know the code.
### command-line help
-the native vm, portable vm, language server, bootstrap compiler, image converter and map generator accept `--help` or `-h`. help exits before reading input or allocating vm memory, so it also works with stdin left open. `bin/zy` reads source from stdin and runs `main`; `bin/zym` runs the embedded image, and compiler images can read source from stdin. run-only images read program input directly. `bin/zclsp --format`, `--annotate` and `--types` remain available for source on stdin; without one of these options it reads language-server protocol messages. `bin/zc0` compiles bootstrap source from stdin to a C genome header on stdout; `bin/mkgenome` converts little-endian 64-bit image words from stdin into that header format. `bin/genmap` accepts a preset or compiler/run mode, optional output directory, and build-time map sizing options. the help for each tool lists its input/output format and memory environment variables where applicable.
+the native vm, portable vm, language server, bootstrap compiler, image checker and map generator accept `--help` or `-h`. help exits before reading input or allocating vm memory, so it also works with stdin left open. `build/bin/zy` reads source from stdin and runs `main`; `build/bin/zym` runs the embedded image, and compiler images can read source from stdin. run-only images read program input directly. `build/bin/zclsp --format`, `--annotate` and `--types` remain available for source on stdin; without one of these options it reads language-server protocol messages. `build/bin/zc0` compiles bootstrap source from stdin to a genome image on stdout; `build/bin/mkgenome` checks an image from stdin against its memory map and copies it to stdout, and when the input is a compiler's error report instead of an image it prints the report. `build/bin/genmap` accepts a preset or compiler/run mode, optional output directory, and build-time map sizing options. the help for each tool lists its input/output format and memory environment variables where applicable.
-the same early help option is available in `tinyvm.sh`, `tinyvm.bat`, `src/tools/zcc`, the public `src/tools/*.sh` build scripts, and the javascript vm and extraction utilities. `src/tools/zcc [-o OUT] [-k] FILE.zc` builds a standalone run-only binary. `src/tools/automap.sh run|small|default|lsp OUTDIR FILE...` generates a sized map and image; `src/tools/embed.sh std|icon|all [--check]` refreshes embedded modules, while its `lsp OUT` form writes source providers. `src/tools/mklsp.sh [OUTDIR]`, `src/tools/mklspwasm.sh [OUTFILE.wasm]` and `src/tools/mkvsix.sh [OUTFILE.vsix]` build the language server and extension. the javascript vm accepts an optional source file; without one it reads source from stdin. the extraction utilities take no input arguments and read the generated headers from their documented paths. `python3 tests/drivers/cli_help_test.py` checks the native, micro, LSP, bootstrap and image tools, shell scripts, and javascript utilities with open stdin and invalid memory settings, rejects unknown options, and confirms the language server source options still work.
+the same early help option is available in `tinyvm.sh`, `tinyvm.bat`, `src/tools/zcc`, the public `src/tools/*.sh` build scripts, and the javascript vm and extraction utilities. `src/tools/zcc [-o OUT] [-k] FILE.zc` builds a standalone run-only binary. `src/tools/automap.sh run|small|default|lsp OUTDIR FILE...` generates a sized map (`OUTDIR/memmap.h`) and image (`OUTDIR/genome.bin`). `src/tools/mklsp.sh [OUTDIR]`, `src/tools/mklspwasm.sh [OUTFILE.wasm]` and `src/tools/mkvsix.sh [OUTFILE.vsix]` build the language server and extension. `src/tools/findcc.sh [--wasm]` prints a compiler with `#embed` (and the wasm32 target), and `src/tools/bootstrap-check.sh` runs the first two bootstrap stages and prints the compiler's diagnostics on its own source. the javascript vm accepts an optional source file; without one it reads source from stdin. `src/js/mklayout.js` and `src/js/extract_lsp.js` write the javascript layout and the language server's image from the maps and images under `build/`. `python3 tests/drivers/cli_help_test.py` checks the native, micro, LSP, bootstrap and image tools, shell scripts, and javascript utilities with open stdin and invalid memory settings, rejects unknown options, and confirms the language server source options still work.
-`make js` regenerates the JavaScript bundle into `build/js/` and needs Node.js and the TypeScript compiler (`tsc`). running the built `build/js/tinyvm.js` directly needs Node.js only.
+`make js` writes the javascript vm into `build/js/`: `tinyvm.js`, its `layout.js` and a copy of `genome.bin` that it reads at start-up. it needs node.js and the typescript compiler (`tsc`). running the built `build/js/tinyvm.js` directly needs node.js only.
### interactive runners
@@ -74,12 +74,14 @@ windows needs clang or gcc and powershell for the tests. the batch file is the s
on arm64, `src/core/core.c` uses its jit by default. other hosts use the interpreter. `-DZYINTERP` forces interpretation; `-DZYLLVM` enables the llvm backend when its headers and libraries are available. direct builds from the root are:
```sh
-mkdir -p bin
-cc -O2 -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o bin/zy
-cc -O2 -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o bin/zym
-cc -O2 -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o bin/zy-llvm $(llvm-config --ldflags --libs)
+make genome
+clang -O2 -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o build/bin/zy
+clang -O2 -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -Ibuild/core/map --embed-dir=build/core -o build/bin/zym
+clang -O2 -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o build/bin/zy-llvm $(llvm-config --ldflags --libs)
```
+`-I` finds the generated `memmap.h` and `--embed-dir` finds `genome.bin`; pointing them at another map and image (a `src/tools/automap.sh` output directory) builds a core for that machine.
+
`J` must match the host port: the supplied ports use `int`. a source unit ends at eof or a backtick outside a comment or literal; bytes after that backtick remain available to the program.
`ZYTRACE=1` logs translations and installations to stderr. with tracing, `ZYO3DUMP=1` also prints region micro-ops. on llvm builds, `ZYHOT=n` sets the compilation threshold, `ZYPASSES` selects the pass pipeline, and `ZYNOLLVM=1` disables llvm compilation.
@@ -104,7 +106,7 @@ check mode records errors instead of printing them and continues after an error,
the server runs on its own memory map (`genmap --preset lsp`), which has a bigger genome slot and a smaller frame stack than the default, and it keeps its documents in a store of its own, 4 chars to a word.
```cmd
-tinyvm.bat build lsp # bin\zclsp.exe
+tinyvm.bat build lsp # build\bin\zclsp.exe
src/tools/mklsp.sh build/lsp # elsewhere: build/lsp/zclsp
tinyvm.bat lsp examples\opt_test.zc
```
@@ -119,14 +121,15 @@ the `editors/vscode` directory contains the complete VS Code extension for ZC:
- **language configuration**: bracket matching, indentation rules, auto-closing pairs, and comments (`//`, `/* */`).
- **language client**: a small node client (`client/extension.js`) with no packages. it speaks the protocol properly (byte lengths, `textDocument` wrappers, `didClose`), restarts the server if it exits and sends the open documents again.
-build a package with `bash src/tools/mkvsix.sh`, then use "extensions: install from vsix" in vscode. the bundled wasm server needs no native executable. `zc.lsp.serverPath` optionally selects a native server. see [the extension guide](editors/vscode/README.md) for settings and logs.
+build a package with `make vsix` (or `bash src/tools/mkvsix.sh`), which writes `build/zc-language-support.vsix`, then use "extensions: install from vsix" in vscode. the server files are staged in `build/editors/vscode/server/` and the extension is packed from a copy in `build/vsix/`, so the tree is never written to; to run the extension from source, open that copy in an extension development host. the bundled wasm server needs no native executable. `zc.lsp.serverPath` optionally selects a native server. see [the extension guide](editors/vscode/README.md) for settings and logs.
### javascript & typescript runtime
a complete, self-contained 64-bit TinyVM core implemented in JavaScript and TypeScript is provided in `src/js/`:
- `src/js/tinyvm.ts` (compiled to `build/js/tinyvm.js`): Implements the 64-bit word machine on `BigInt64Array` with byte addressing (`Uint8Array` / `DataView`), dual stacks, all 22 base opcodes, byte/float opcodes (612–619), system calls, and custom virtual enhancement execution (`xrun`).
-- `build/js/genome.json`: the compiler genome loaded into VM memory, generated. `node src/js/extract_genome.js` rebuilds it from `build/core/genome.h`, and `tests/test.sh` checks it is current.
+- `build/js/genome.bin`: the compiler genome, the same image the native cores embed, which `tinyvm.js` reads from beside itself. `build/js/layout.js` holds the memory map's constants, written by `node src/js/mklayout.js` from `build/core/map/memmap.h`.
+- file syscalls (open, read, write, close) reach node's file system when the vm is run from the command line, so `include "path"` and `embed` work there; a vm created by other code (the editor's) has no file access unless it passes `files: true`.
run any `.zc` program in Node.js:
```cmd
@@ -139,11 +142,11 @@ tinyvm.bat js examples/primes.zc
### web (webassembly)
-`src/web/` runs the compiler in a browser. `src/web/build.sh` compiles the microcore and genome to `src/web/zc.wasm` with clang's wasm32 target, without emscripten or libc. `src/web/port_wasm.c` provides the port and the 64-by-64-to-128-bit multiply used by the core. `src/web/zc.js` loads the module and exposes `machine.run(source)`, which returns the output produced by `zy < file.zc` from a fresh instance. `src/web/index.html` provides an editor, examples list, and output pane. serve the repository with `python3 -m http.server` and open `http://localhost:8000/src/web/index.html`.
+`src/web/` runs the compiler in a browser. `make web` compiles the microcore and genome to `build/web/zc.wasm` with clang's wasm32 target (through `src/web/build.sh`), without emscripten or libc, and copies the page and its loader beside it. `src/web/port_wasm.c` provides the port and the 64-by-64-to-128-bit multiply used by the core. `src/web/zc.js` loads the module and exposes `machine.run(source)`, which returns the output produced by `zy < file.zc` from a fresh instance. `src/web/index.html` provides an editor, examples list, and output pane. serve `build/web` with `python3 -m http.server -d build/web` and open `http://localhost:8000/`.
```
-bash src/web/build.sh
-node tests/drivers/wasm_test.js
+make web
+node tests/drivers/wasm_test.js build/web/zc.wasm
```
## zc, the language
@@ -176,6 +179,16 @@ the array intrinsics also accept receiver syntax: `a.len()`, `a.cap()`, `a.push(
`include std;` also registers read-only array algorithms: `find` and `find_if` return a matching index, `contains`, `any`, and `all` return a boolean word, `count` counts matches, `map` transforms elements into a new handle, `filter` copies matching elements into a new handle, and `reduce` folds them from left to right. `any` is false and `all` is true for an empty input; `reduce` returns its initial value. they accept mutable fixed or growable arrays; support for const-qualified arrays is unverified. lambda parameter types follow the element type, map's result type follows the callback return, and filter preserves the input element type. algorithms visit the input's initial length in index order, so callbacks must not change its length. map/filter results are independent handles that shallow-share reference elements with the collector. aggregate-by-value elements are not supported by `push`, `pop`, `map`, or `filter`. [the runnable example](examples/learn/31-array-algorithms.zc) shows inferred callbacks and search.
+`embed "path" as [type] name;` declares `name` with the bytes of a file, read when the unit is compiled; the path is found the way an `include` path is (next to the including file, then in the workspace folder, then from the working directory). with no type, or `bstr`, `name` is a byte string of the whole file. `char*` gives a word string, one character per word. a number type reads the bytes little-endian: an integer type takes at most its size and zero-extends a shorter file (sign-extends for `i8`, `i16` and `i32`), and `float` or `f32` needs exactly 8 or 4 bytes. `T[]` and `T[n]` lay the bytes out as array elements of a number type, packed for the sized types and one byte per element for `char[]`; a file that is not a whole number of elements, or that does not fit `T[n]`, is an error. at the top level `name` is a global and the bytes sit in the image; in a function it is a local, and an array is copied into the frame each time the declaration runs. the compiler uses it for the libraries it carries (`embed "src/lib/std.zc" as bstr std_embedded;`).
+
+```
+include rt;
+embed "tests/data/embed/text.txt" as greeting; // a bstr
+embed "tests/data/embed/answer.bin" as int answer;
+embed "tests/data/embed/bytes.bin" as u8[] table;
+fn main() { rt.put_s(greeting); rt.put_n(answer + table[0]); return 0; }
+```
+
a unit is read whole and scanned for function signatures before it compiles, so calls can come before definitions, tags included, and prototypes are optional. `main` runs after the whole program has compiled. use `void f()` for a function with no result and `return;` to leave it; value-returning functions use an explicit result type or the inferred `fn` form.
the runtime api lives in the `rt` namespace and requires `include rt;`: `rt.put_c`, `rt.get_c`, `rt.eof`, `rt.put_s`, `rt.put_n`, `rt.halt`, `rt.len_w`, `rt.eq_w`, `rt.copy_w`, `rt.fill_w`, the packed-string helpers `rt.len_s`, `rt.at_s`, `rt.slice_s`, `rt.cat_s`, `rt.eq_s`, `rt.s_to_n`, `rt.n_to_s` and `rt.put_bs`, allocation and ownership helpers such as `rt.new`, `rt.new_t`, `rt.free`, `rt.gc`, `rt.memreport`, and host functions including `rt.syscall` and `rt.readfile`. their signatures and descriptions are in the runtime source comments and built-in hover table. `src/lib/std.zc` declares the `std` namespace; its static groups are called as `std.Group.name(args)`: `std.Text`, `std.Num`, `std.Host`, `std.Bytes`, `std.Arena`, and `std.Buf`. the constructors `std.arena(n)` and `std.bnew(n)` and globals such as `std.argc` and `std.argv` are plain declarations in that namespace. capture uses static functions: `std.capture.start()` begins output capture and `std.capture.end(buf)` ends it and returns the buffer.
@@ -213,7 +226,18 @@ fn main() {
**u** transfers the object to the enclosing scope, where it becomes m. transfer occurs through `return`, a block expression value, or assignment to a variable in the directly enclosing block. returning an owned local transfers it automatically; a `.u` result declaration provides the contract to callers before the function body is available. an owned u object cannot fall out of scope or have its result discarded. bind the result to an owner and call `rt.free`, or pass it to a consuming parameter.
-explicit recasts use `u*x`, `d*x`, or `m*x`, with no space before the star. a space is ordinary multiplication by a variable of that name. recasts remain available for low-level code, while checked returns, `.d` calls, frees, and owner assignments infer common transfers. a declaration can carry a tag too, `.u r;`, and then whatever lands in it gets that tag.
+explicit recasts use `u*x`, `d*x`, or `m*x`, with no space before the star. a space is ordinary multiplication by a variable of that name. recasts remain available for low-level code, while checked returns, `.d` calls, frees, and owner assignments infer common transfers. a declaration can carry a tag too, `.u r;`, and then whatever lands in it gets that tag. recasting a local that owns nothing is an error that names it.
+
+an owner can move into another local whose object was released first: `rt.free(r); r = replacement;` gives `r` the replacement's object and retires `replacement`, when `replacement` lives in the same block as `r` or a deeper one. an owner cannot be copied into a local that still owns something.
+
+a global can borrow a local owner for a bounded stretch: `g = m*x;` lends `x` to `g` while `x` keeps ownership and can still be used, and the lend ends when the same block assigns `g` again (usually restoring its old value). the analyzer rejects giving `x` away, returning, or leaving that block while the lend is open, so `g` never points at a released object. this is how an output stream is pointed at a local scratch buffer:
+
+```
+word saved = rt.outbuf;
+rt.outbuf = m*buffer;
+render();
+rt.outbuf = saved;
+```
parameters: an untagged parameter is a lend, and the compiler infers how a checked body uses it. `.m` states a borrow contract and `.d` receives ownership. Passing an owned value to `.d` transfers it automatically, so `take(p)` can replace `take(d*p)`. A checked borrow can return an alias of its parameter, but it cannot return an alias of a local owner. A fresh result passed to a proved non-retaining function, as in `rt.put_s(message())`, is held in a compiler slot and released after the call. Unknown or retaining callees need an explicit contract or a named owner.
@@ -319,7 +343,7 @@ ordinary comments immediately above a declaration provide its hover documentatio
### reflection
-`typeof(e)` and `typeof(T)` give a type record and `sizeof(...)` its size. both work on any expression, which is compiled dry and never runs. at runtime: `tkind(t)` (one of the `TY_` constants), `tsize`, `tname`, `tlen`, `tnfields`, `tfname(t, i)`, `tftype`, `tfoff`, `tret`, `tnparams`, `tparam`, `tparamtag` (the u/m/d tag of a parameter), `tretu`, `tnargs` and `targ` for generic instances, and `tprint(t)`. `sigof(f)` gives the type of a function by address (untyped ones get a signature built from their arity and tags), `sigofs("name")` by name, and `rt.typeof_obj(p)` reads the type stamped on a heap object (0 when the header has no type yet: `rt.new(n)` or `rt.new_raw`).
+`typeof(e)` and `typeof(T)` give a type record and `sizeof(...)` its size. both work on any expression, which is compiled dry and never runs. the readers are compiler functions, so they need `include run;`: `run.tkind(t)` (one of the `run.TY_` constants), `run.tsize`, `run.tname`, `run.tlen`, `run.tnfields`, `run.tfname(t, i)`, `run.tftype`, `run.tfoff`, `run.tret`, `run.tnparams`, `run.tparam`, `run.tparamtag` (the u/m/d tag of a parameter), `run.tretu`, `run.tnargs` and `run.targ` for generic instances, and `run.tprint(t)`. `run.sigof(f)` gives the type of a function by address (untyped ones get a signature built from their arity and tags), `run.sigofs("name")` by name, and `rt.typeof_obj(p)` reads the type stamped on a heap object (0 when the header has no type yet: `rt.new(n)` or `rt.new_raw`).
### the typechecker
@@ -485,7 +509,7 @@ tables: `run.add_binop("**", 11, "ipow c")` adds or replaces a binary operator w
## the bootstrap
-`src/tools/zc0.c` compiles the subset of zc used by `src/compiler/compiler.zc` (without the analyzer, types, quote, parse words, or block expressions, but with `enhance` declarations) into the first genome. that genome compiles `src/compiler/compiler.zc` again in the VM using the full compiler, analyzer, and native division code. the compiler passes its lifetime checks and compiles itself into identical words; the test suite checks this fixpoint on the jit and llvm backends when available. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements.
+`src/tools/zc0.c` compiles the subset of zc used by `src/compiler/compiler.zc` (without the analyzer, types, quote, parse words, or block expressions, but with `enhance` and top-level `embed` declarations) into the first genome, `build/s0/genome.bin`. a core built around that image compiles `src/compiler/compiler.zc` again in the VM using the full compiler, analyzer, and native division code, and `mkgenome` accepts the result only if it is an image (a compiler that rejects its own source prints diagnostics instead, which `mkgenome` shows). a core built around the second image compiles the source a third time; the two self-compiled images must be identical, and the first becomes `build/core/genome.bin`. the test suite checks this fixpoint on the interpreter, microcore, jit and llvm backends when available. in the zc0 subset `u`, `m` and `d` are recast tags, not names. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements. an image build (`build_image(org, dst)`) compiles with only the image's names, keywords, and built-in types visible, writes code for the requested address, and appends a symbol table containing its names and enhancements.
## the instruction set
@@ -515,7 +539,7 @@ stack effects: `!` is `(v addr --)`, `$` is `(n -- copy of the nth item)` so `0
see [memory configuration](docs/design/memory.md) for build controls, runtime reservations, and host limits.
-the initial layout reserves code, compiler tables, the genome, and stacks below `PW`. the heap starts at `PW` on hosted growing maps. tables and execution stacks grow when needed, and emitted code uses stable heap segments after its initial region fills. native code-address metadata grows with it. `MW` selects the default host memory limit (1 GiB, `0x8000000` words, on the default build); `TINYVM_MEMORY_WORDS` and `TINYVM_MAX_MEMORY_WORDS` configure startup memory and its growth limit. the native core reserves stable backing up to the startup maximum, while portable hosts replace their backing allocation when they grow. a build can select another default with `-DMW=...`; `src/core/memmap.h` rejects a value smaller than the initial heap.
+the initial layout reserves code, compiler tables, the genome, and stacks below `PW`. the heap starts at `PW` on hosted growing maps. tables and execution stacks grow when needed, and emitted code uses stable heap segments after its initial region fills. native code-address metadata grows with it. `MW` selects the default host memory limit (1 GiB, `0x8000000` words, on the default build); `TINYVM_MEMORY_WORDS` and `TINYVM_MAX_MEMORY_WORDS` configure startup memory and its growth limit. the native core reserves stable backing up to the startup maximum, while portable hosts replace their backing allocation when they grow. a build can select another default with `-DMW=...`; the generated `memmap.h` rejects a value smaller than the initial heap. the default map's header is generated by `genmap --preset default` into `build/core/map/` from the `@map` const blocks in `src/runtime/rt.zc` and `src/compiler/compiler.zc`, which stay the source of truth.
```
0x000000..0x07FFFF code: the program, with globals between functions (CODE_END)
@@ -530,7 +554,7 @@ the initial layout reserves code, compiler tables, the genome, and stacks below
0x1C5000..0x224FFF the heap to start with (0x60000 words), then it grows up to MW
```
-the numbers describe the default build's initial reservations. compact compiler and run-only maps start with smaller reservations and also support growth. `bin/genmap` writes matching layouts (see "smaller machines"); `--preset fixed` retains the older bounded heap for compatibility. the host answers `core.syscall(9, k)` with facts about the map (0 total words, 1 heap start, 2 heap end for now, 3 bytes). argument 4 requests memory growth, and argument 5 changes the growth limit within the host's backing constraints.
+the numbers describe the default build's initial reservations. compact compiler and run-only maps start with smaller reservations and also support growth. `build/bin/genmap` writes matching layouts (see "smaller machines"); `--preset fixed` retains the older bounded heap for compatibility. the host answers `core.syscall(9, k)` with facts about the map (0 total words, 1 heap start, 2 heap end for now, 3 bytes). argument 4 requests memory growth, and argument 5 changes the growth limit within the host's backing constraints.
calls push the arguments left to right and the callee's prologue pops them into its frame. locals live in the frame at `*0x90000 + slot`. every function leaves exactly one word on the data stack. heap objects have a 5 word header: size, flags with the size class in the high bits, a check word, owner, type.
@@ -548,7 +572,7 @@ platform: the darwin path uses `MAP_JIT`, `pthread_jit_write_protect_np` and `sy
## the microcore
-`src/micro/micro.c` is a portable interpreter for the same genome, small enough to move to new targets, including bare metal. it complements `src/core/core.c` with the memory map, two stacks, base opcodes, call and return, and genome loading at `GORG`. it has no jit, llvm backend, o3 lifter, or enhancement optimizer. it uses `stdint.h` and the shared headers `src/micro/micro_port.h`, `src/core/fops.h`, `src/core/bops.h`, and `src/core/stack.h`, plus the generated genome header; `micro_port.h` includes `memmap.h`. its five host functions are declared in `src/micro/micro_port.h`. bss must be zero before `micro_run`. on a cpu without a 64-bit divider, the compiler may link its own division helpers. the microcore's initial memory comes from the generated map, and hosted ports can configure the initial size and growth limit.
+`src/micro/micro.c` is a portable interpreter for the same genome, small enough to move to new targets, including bare metal. it complements `src/core/core.c` with the memory map, two stacks, base opcodes, call and return, and genome loading at `GORG`. it has no jit, llvm backend, o3 lifter, or enhancement optimizer. it uses `stdint.h` and the shared headers `src/micro/micro_port.h`, `src/core/fops.h`, `src/core/bops.h`, `src/core/stack.h` and `src/core/genome.h`, which embeds the image; `micro_port.h` includes the generated `memmap.h`. its five host functions are declared in `src/micro/micro_port.h`. bss must be zero before `micro_run`. on a cpu without a 64-bit divider, the compiler may link its own division helpers. the microcore's initial memory comes from the generated map, and hosted ports can configure the initial size and growth limit.
the host interface is `src/micro/micro_port.h`. `src/micro/port_linux.c` provides stdin, stdout, syscalls, and stderr diagnostics, matching `src/core/io_linux.c` plus the syscall gate in `src/core/core.c`. syscalls 1 to 6 handle files, environment variables, and unix time; 7 exits, 8 reads a counter, and 9 reports or configures memory. `src/micro/port_stub.c` is the freestanding template: connect `uart_putc` to a transmit register, leave `micro_getc` at end of input, and have `micro_syscall` return -1 for every operation. the runtime then sees failed file opens and no clock. `src/baremetal/port.c` implements the same five functions for the virt boards, where the operations are available. link the stub instead of `port_linux.c` and call `micro_run` from reset.
@@ -564,12 +588,11 @@ what it leaves out, on purpose: native code, the optimizer, o3's register interp
```sh
bash src/tools/automap.sh small build/small
-cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/fops.h src/core/bops.h src/core/stack.h build/small/
-cc -O2 -fwrapv build/small/micro.c build/small/port_linux.c -Ibuild/small -o build/small/zym
+clang -O2 -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -Ibuild/small --embed-dir=build/small -o build/small/zym
build/small/zym < examples/hello.zc
make run PROG=examples/hello.zc
build/run/zymr
-bin/genmap --preset default --check
+build/bin/genmap --preset default --check
```
use `genmap` options to adjust heap, table, and stack sizes at build time. compile the core against the generated headers and the genome for that same layout. `--preset fixed` keeps the historical layout; `--preset default` places the heap above the fixed regions so it can grow. see [memory configuration](docs/design/memory.md) for controls and current limits.