diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /src/tools | |
a
Diffstat (limited to 'src/tools')
| -rwxr-xr-x | src/tools/automap.sh | 58 | ||||
| -rw-r--r-- | src/tools/build.zc | 14 | ||||
| -rw-r--r-- | src/tools/genmap.c | 858 | ||||
| -rw-r--r-- | src/tools/mkgenome.c | 31 | ||||
| -rwxr-xr-x | src/tools/mklsp.sh | 14 | ||||
| -rwxr-xr-x | src/tools/mklspwasm.sh | 20 | ||||
| -rwxr-xr-x | src/tools/mkvsix.sh | 35 | ||||
| -rw-r--r-- | src/tools/zc0.c | 880 | ||||
| -rwxr-xr-x | src/tools/zcc | 54 |
9 files changed, 1964 insertions, 0 deletions
diff --git a/src/tools/automap.sh b/src/tools/automap.sh new file mode 100755 index 0000000..8cefe6a --- /dev/null +++ b/src/tools/automap.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# sizes a memory map to the program it holds, before the program is compiled for it. +# src/tools/automap.sh PRESET OUTDIR FILE... +# PRESET is run, small, default or lsp. DOCS=1 makes the genome with its doc comments. FILE... are appended after the map's own +# rt.zc (and compiler.zc, for the presets that carry the compiler). the result +# is OUTDIR/memmap.h and OUTDIR/genome.h, and they agree with each other. +# +# a genome's length doesn't depend on where it lives (every address is a one +# word literal), so two passes are enough: compile once on the preset's map +# to learn the length, then lay the map out again with a genome slot of exactly +# that length, rounded up to 4096 words, and let a fixed-size map grow if it has +# to. the second compile must come out the same length, and that is checked. +# zc0 reads the map at build time, so it is built again for each layout. +# GENMAP can name a genmap that is already built. +set -e +cd "$(dirname "$0")/../.." +P=$1; O=$2; shift 2 +[ -n "$P" ] && [ -n "$O" ] || { echo "usage: src/tools/automap.sh run|small|default|lsp OUTDIR FILE..." >&2; exit 2; } +CC=${CC:-gcc} +mkdir -p "$O" +ZC0=$O/.zc0 +if [ -z "$GENMAP" ]; then + GENMAP=$O/.genmap + if [ ! -x "$GENMAP" ] || [ src/tools/genmap.c -nt "$GENMAP" ]; then $CC -O2 -w -fwrapv src/tools/genmap.c -o "$GENMAP"; fi +fi +lay() { # $1 = genome slot, or empty for the preset's own + rm -rf "$O/map" + "$GENMAP" --preset "$P" --grow ${1:+--genome $1} -o "$O/map" >/dev/null 2>&1 + $CC -O1 -w -fwrapv src/tools/zc0.c -I"$O/map" -Isrc/core -o "$ZC0" +} +build() { + if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$ZC0" +} +# with DOCS=1 the genome is made again with zc0 built for docs (ZC0DOCS), so the +# image carries the doc comments of the sources and the program can answer +# docofs(). the image is the same code either way; the docs are stored after it. +docs() { + $CC -O1 -w -fwrapv -DZC0DOCS src/tools/zc0.c -I"$O/map" -Isrc/core -o "$O/.zc0docs" + if [ -f "$O/map/compiler.zc" ]; then cat "$O/map/rt.zc" "$O/map/compiler.zc" "$@"; else cat "$O/map/rt.zc" "$@"; fi | "$O/.zc0docs" +} +len() { grep -m1 '#define GLEN' "$1" | awk '{print $3}'; } +lay "" +build "$@" > "$O/genome.h" +glen=$(len "$O/genome.h") +slot=$(( (glen + 4095) / 4096 * 4096 )) +lay "$slot" +build "$@" > "$O/genome.h" +if [ -n "$DOCS" ]; then + lay $((slot + 65536)) # room for the image with its docs while it is being made + docs "$@" > "$O/genome.h" + glen=$(len "$O/genome.h") + slot=$(( (glen + 4095) / 4096 * 4096 )) + lay "$slot" + docs "$@" > "$O/genome.h" +fi +[ "$(len "$O/genome.h")" = "$glen" ] || { echo "automap: the genome changed length with the map" >&2; exit 1; } +cp "$O/map/memmap.h" "$O/map/map.txt" "$O/" +echo "automap $P: genome $glen words in a slot of $slot, $(grep -m1 '^MW' "$O/map/map.txt")" diff --git a/src/tools/build.zc b/src/tools/build.zc new file mode 100644 index 0000000..547b70f --- /dev/null +++ b/src/tools/build.zc @@ -0,0 +1,14 @@ +// compiles the rest of stdin (after the backtick) as a genome image at GORG +// and writes it to stdout as little-endian 64-bit words. +docs on; +// writes the compiled genome image to stdout as little-endian 64-bit words. +int main() { + int n = build_image_grow(GORG, 4096), i = 0, w; + while (i < n) { + w = image_words[i]; + put_c(w); put_c(w >> 8); put_c(w >> 16); put_c(w >> 24); + put_c(w >> 32); put_c(w >> 40); put_c(w >> 48); put_c(w >> 56); + i++; + } + return 0; +} diff --git a/src/tools/genmap.c b/src/tools/genmap.c new file mode 100644 index 0000000..dc1971c --- /dev/null +++ b/src/tools/genmap.c @@ -0,0 +1,858 @@ +/* one layout for every memory size. addresses in a genome are literals, so + each size is its own image. this tool writes that image's memmap.h and + the runtime (and, for a compiler build, compiler.zc) with the same const + names as the checked-in 1,048,576-word sources. + + run from the repo root: + + cc -O2 src/tools/genmap.c -o genmap + ./genmap --preset small -o build/small + ./genmap --preset run -o build/run + ./genmap --preset default --check + + ./genmap --mode compiler --heap-lo 8192 --heap 20480 --types 8192 --genome 245760 --frame 2048 --return 512 --mark 512 --data 3072 --snap-cap 1000 --quote-cap 300 -o build/small + ./genmap --mode run --words 20480 -o build/run + + --preset default puts the code area, the compiler tables, the genome and + the stacks first and the heap last, so the heap can grow at run time up to + the total memory size (PW is where the fixed part ends; the host's MW is + the total). --preset fixed is the historical 1M map (a fixed heap below + the frame pointer, types at the top of it). --mode packs from the bottom. omitting + --words rounds MW up to a multiple of 4096, which is what the arm64 jit + needs in order to encode cmp xn, #MW. the checked-in sources stay the + default map; --check fails if they drift from that preset. */ +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <errno.h> +#ifdef _WIN32 +#include <direct.h> +#endif + +static int align_up(int x, int a) { return (x + a - 1) & ~(a - 1); } + +static void die(const char *m) { fprintf(stderr, "genmap: %s\n", m); exit(1); } + +static int parse_num(const char *s) +{ + char *e; + long v; + if (!s || !*s) die("missing number"); + v = strtol(s, &e, 0); + if (*e || v <= 0 || v > 0x7fffffff) die("bad number"); + return (int)v; +} + +static char *slurp(const char *path) +{ + FILE *f = fopen(path, "rb"); + long n; char *b; + if (!f) { perror(path); exit(1); } + if (fseek(f, 0, SEEK_END) || (n = ftell(f)) < 0 || fseek(f, 0, SEEK_SET)) die("seek"); + b = malloc((size_t)n + 1); + if (!b || fread(b, 1, (size_t)n, f) != (size_t)n) die("read"); + b[n] = 0; + fclose(f); + return b; +} + +static void emit_file(const char *path, const char *s) +{ + FILE *f = fopen(path, "wb"); + if (!f) { perror(path); exit(1); } + fputs(s, f); + if (fclose(f)) die("write"); +} + +/* replace the text between the line that is `begin` and the next line that + is `end`. the marker lines stay. */ +static char *splice(const char *src, const char *begin, const char *end, const char *repl) +{ + const char *a = strstr(src, begin), *b; + char *out; size_t n, nr; + if (!a) { fprintf(stderr, "genmap: missing %s\n", begin); exit(1); } + a += strlen(begin); + if (*a == '\n') a++; + b = strstr(a, end); + if (!b) { fprintf(stderr, "genmap: missing %s\n", end); exit(1); } + nr = strlen(repl); + n = (size_t)(a - src) + nr + strlen(b) + 1; + out = malloc(n); + if (!out) die("oom"); + memcpy(out, src, (size_t)(a - src)); + memcpy(out + (a - src), repl, nr); + strcpy(out + (a - src) + nr, b); + return out; +} + +static char *replace_all(const char *src, const char *from, const char *to) +{ + const char *p = src, *hit; + size_t nf = strlen(from), nt = strlen(to), n = 0, count = 0; + char *out, *w; + while ((hit = strstr(p, from))) { count++; p = hit + nf; } + if (!count) { fprintf(stderr, "genmap: missing %s\n", from); exit(1); } + out = malloc(strlen(src) + count * (nt - nf) + 1); + if (!out) die("oom"); + p = src; w = out; + while ((hit = strstr(p, from))) { + memcpy(w, p, (size_t)(hit - p)); w += hit - p; + memcpy(w, to, nt); w += nt; + p = hit + nf; + n++; + } + strcpy(w, p); + (void)n; + return out; +} + +typedef struct { + int MW, HEAP_LO, HEAP_HI, ZFP, GORG, FBOT, FTOP, RS0, MARK0, MARKN, DSLO, DSHI; + int TW, CODE_END, GROW; /* TW: total words (MW is where the fixed map ends); CODE_END: program words end */ + int IHN, R_IH, R_POOL, R_POOLEND, NS, R_SYM; + int sname, skind, sval, ssig, sflag, sfix, snext; + int NL, lname, lslot, lhome, lstate, lowner, lused, larr, lown; + int NF, fsite, fnext, R_SNAP, R_QBUF, R_SRC, R_SEQ; + int bname, bprec, bseq, blen, uname, useq, ulen, aname, abin, kops; + int R_CTX, R_LOOP, R_ASM, cend, glen_need, typesz; + int ASM_LABELS, ASM_REFS, ASM_WORDS, EXIT_WORDS; + int ENUM_MEMBERS, TAGGED_ENUMS, STRUCT_METHODS, GENERATED_WORDS; + int RANGE_FACTS, JOIN_PATHS, O3_REGIONS; + const char *name; +} Map; + +/* what actually differs between builds. zero means "use the mode default" + except where a preset fills the field itself before layout. */ +typedef struct { + const char *name; + int compiler; + int words; + int heap_lo, heap, types, zfp_pad; + int heap_top, code_end, total; /* heap above the stacks, free to grow up to total words */ + int genome, frame, ret, mark, data; + int ihn, pool, ns, nl, nf, snap, qbuf, src, seq, ctx, loop; + int snap_cap, quote_cap; /* 0 keeps the source caps (8000 and 2700) */ + int binary, unary, assignments, keywords; + int asm_labels, asm_refs, exits; + int enum_members, tagged_enums, struct_methods, generated_words; + int facts, paths, regions; + int type_formula; /* 0 keeps ZFP-16384; else the copy says ZFP-N */ +} Spec; + +static void need(int cond, const char *m) { if (!cond) die(m); } + +static void check_common(const Map *m) +{ + need(m->MW && (m->MW % 4096) == 0, "MW must be a positive multiple of 4096"); + need(m->HEAP_LO >= 256 && m->HEAP_HI > m->HEAP_LO, "heap"); + need(m->HEAP_HI - m->HEAP_LO >= 8 && ((m->HEAP_HI - m->HEAP_LO) & 7) == 0, "heap size must be a multiple of eight words"); + if (m->GROW) { + need(m->HEAP_LO == m->MW, "a growing heap starts where the fixed map ends"); + need(m->TW >= m->HEAP_HI && (m->TW % 4096) == 0, "total memory"); + need(m->CODE_END >= 256 && m->ZFP >= m->CODE_END && m->ZFP + 8 < m->MW, "frame pointer"); + } else { +/* the historical map sets ZFP == HEAP_HI: the type area overlaps the heap */ + need(m->ZFP >= m->HEAP_HI && m->ZFP + 8 < m->MW, "frame pointer"); + } + need(m->GORG > m->ZFP + 8, "genome overlaps the frame pointer"); + need(m->GORG + m->glen_need <= m->FBOT, "genome reservation collides with the frame stack"); + need(m->FBOT + 64 < m->FTOP && m->FTOP == m->RS0, "frame stack"); + need(m->MARK0 >= m->FTOP + 64 && m->MARK0 + m->MARKN <= m->DSLO, "mark stack"); + need(m->DSHI == m->MW - 16 && m->DSHI - m->DSLO >= 1100 + 1024, "data stack"); + need(m->DSLO > m->MARK0, "data stack overlaps the mark stack"); +} + +static void tables_tight(Spec *s) +{ + s->ihn = 2048; s->pool = 16384; s->ns = 1280; s->nl = 512; s->nf = 2048; + s->snap = 1024; s->qbuf = 1024; s->src = 1024; s->seq = 1024; + s->ctx = 1024; s->loop = 512; +} + +/* the default map's tables: room for a large program and for several generic + libraries, in words nobody touches until a program needs them */ +static void tables_big(Spec *s) +{ + s->ihn = 32768; s->pool = 0x40000; s->ns = 16384; s->nl = 512; s->nf = 32768; + s->snap = 16384; s->qbuf = 16384; s->src = 1024; s->seq = 2048; + s->ctx = 2048; s->loop = 1024; +} + +static void tables_historical(Spec *s) +{ + s->ihn = 8192; s->pool = 0x9F00; s->ns = 4096; s->nl = 512; s->nf = 8192; + s->snap = 8192; s->qbuf = 8192; s->src = 1024; s->seq = 2048; + s->ctx = 2048; s->loop = 1024; +} + +static void preset_small(Spec *s) +{ + memset(s, 0, sizeof *s); + s->name = "small"; + s->compiler = 1; + s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000; + s->heap_lo = 8192; s->heap = 20480; s->types = 8192; + s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; + tables_tight(s); + s->snap_cap = 1000; s->quote_cap = 300; s->snap = 1000; s->qbuf = 900; s->type_formula = 8192; +} + +static void preset_run(Spec *s) +{ + memset(s, 0, sizeof *s); + s->name = "run"; + s->heap_top = 1; s->code_end = 512; s->total = 0x8000000; + s->heap_lo = 512; s->heap = 16384; s->zfp_pad = 32; + s->genome = 15600; s->frame = 1024; s->ret = 256; s->mark = 256; s->data = 2560; +} + +static void preset_default(Spec *s) +{ + memset(s, 0, sizeof *s); + s->name = "default"; + s->compiler = 1; + s->heap_top = 1; s->code_end = 0x80000; s->types = 65536; + s->heap = 0x60000; s->total = 0x8000000; + s->genome = 0x50000; s->frame = 0x40000; s->ret = 0x10000; s->mark = 0x8000; + s->data = 0x7FF0; + tables_big(s); +} + +/* the map before the heap moved to the top: a fixed heap between the code and + the frame pointer, the type records at the end of it, 1,081,344 words */ +static void preset_fixed(Spec *s) +{ + memset(s, 0, sizeof *s); + s->name = "fixed"; + s->compiler = 1; + s->words = 0x108000; + s->heap_lo = 0x40000; s->heap = 0x60000; + s->genome = 0x28000; s->frame = 0x10000; s->ret = 0x8000; s->mark = 0x4000; + tables_historical(s); +} + +/* the language server: the compiler's own growing map. the document store, the + diagnostics and the check's sets are heap blocks now, so nothing in the server + is at a fixed address and the heap grows with the documents */ +static void preset_lsp(Spec *s) +{ + preset_default(s); + s->name = "lsp"; +} + +static void mode_defaults(Spec *s, int compiler) +{ + memset(s, 0, sizeof *s); + s->name = compiler ? "compiler" : "run"; + s->compiler = compiler; + if (compiler) { + s->heap_top = 1; s->code_end = 8192; s->total = 0x8000000; + s->heap_lo = 8192; s->heap = 20480; s->types = 8192; s->type_formula = 8192; + s->genome = 245760; s->frame = 2048; s->ret = 512; s->mark = 512; s->data = 3072; + tables_tight(s); + } else { + s->heap_top = 1; s->code_end = 512; s->total = 0x8000000; + s->heap_lo = 512; s->heap = 16384; s->zfp_pad = 32; + s->genome = 15600; s->frame = 1024; s->ret = 256; s->mark = 256; s->data = 2560; + } +} + +/* pack every region from the bottom. a requested MW larger than that keeps + the same bases and gives the spare words to the data stack. */ +static void check_layout_size(const Spec *s) +{ + long long size = (s->heap_top ? s->code_end : s->heap_lo) + (long long)s->heap + s->types + s->zfp_pad; + size += (long long)s->genome + s->frame + s->ret + s->mark + s->data + 8192; + if (s->compiler) { + size += (long long)s->ihn + s->pool + 8LL * s->ns + 10LL * s->nl + 2LL * s->nf; + size += (long long)s->snap + s->qbuf + s->src + s->seq + s->ctx + s->loop; + size += 4LL * (s->binary ? s->binary : 64) + 3LL * (s->unary ? s->unary : 32); + size += 2LL * (s->assignments ? s->assignments : 64) + (s->keywords ? s->keywords : 512); + size += 2LL * (s->asm_labels ? s->asm_labels : 64) + 2LL * (s->asm_refs ? s->asm_refs : 256); + size += s->exits ? s->exits : 2560; + } + need(size <= 0x7fffffff, "layout is too large"); + if (s->words && s->heap_top) need((long long)s->words + s->heap + 4095 <= 0x7fffffff, "heap end is too large"); +} + +static void layout(const Spec *s, Map *m) +{ + int a; + check_layout_size(s); + memset(m, 0, sizeof *m); + m->name = s->name; + m->glen_need = s->genome; + m->typesz = s->types; + if (s->heap_top) { + m->GROW = 1; + m->CODE_END = s->code_end; + m->ZFP = m->CODE_END + s->types + s->zfp_pad; + } else { + m->HEAP_LO = s->heap_lo; + m->HEAP_HI = s->heap_lo + s->heap; + m->CODE_END = m->HEAP_LO; + m->ZFP = m->HEAP_HI + s->types + s->zfp_pad; + } + if (s->compiler) { + m->IHN = s->ihn; m->NS = s->ns; m->NL = s->nl; m->NF = s->nf; + a = m->ZFP + 256; + m->R_IH = a; a += m->IHN; + m->R_POOL = a; a += s->pool; m->R_POOLEND = a; a += 256; + m->R_SYM = m->sname = a; a += m->NS; + m->skind = a; a += m->NS; + m->sval = a; a += m->NS; + m->ssig = a; a += m->NS; + m->sflag = a; a += m->NS; + m->sfix = a; a += m->NS; + m->snext = a; a += m->NS; + m->lname = a; a += m->NL; + m->lslot = a; a += m->NL; + m->lhome = a; a += m->NL; + m->lstate = a; a += m->NL; + m->lowner = a; a += m->NL; + m->lused = a; a += m->NL; + m->larr = a; a += m->NL; + m->lown = a; a += m->NL; + m->fsite = a; a += m->NF; + m->fnext = a; a += m->NF; + m->R_SNAP = a; a += s->snap; + m->R_QBUF = a; a += s->qbuf; + m->R_SRC = a; a += s->src; + m->R_SEQ = a; a += s->seq; + { + int binary = s->binary ? s->binary : 64, unary = s->unary ? s->unary : 32; + int assignments = s->assignments ? s->assignments : 64, keywords = s->keywords ? s->keywords : 512; + m->bname = a; a += binary; + m->bprec = a; a += binary; + m->bseq = a; a += binary; + m->blen = a; a += binary; + m->uname = a; a += unary; + m->useq = a; a += unary; + m->ulen = a; a += unary; + m->aname = a; a += assignments; + m->abin = a; a += assignments; + a += 32; + m->kops = a; a += keywords; + } + m->R_CTX = a; a += s->ctx; + m->R_LOOP = a; a += s->loop; + m->R_ASM = a; + m->ASM_LABELS = s->asm_labels ? s->asm_labels : 64; + m->ASM_REFS = s->asm_refs ? s->asm_refs : 256; + { + long long words = 2LL * m->ASM_LABELS + 2LL * m->ASM_REFS; + need(words <= 0x7fffff00, "assembly reservation is too large"); + m->ASM_WORDS = align_up((int)words, 256); + if (!s->asm_labels && !s->asm_refs) m->ASM_WORDS = 1024; + } + m->EXIT_WORDS = s->exits ? s->exits : 2560; + m->ENUM_MEMBERS = s->enum_members ? s->enum_members : 128; + m->TAGGED_ENUMS = s->tagged_enums ? s->tagged_enums : 16; + m->STRUCT_METHODS = s->struct_methods ? s->struct_methods : 32; + m->GENERATED_WORDS = s->generated_words ? s->generated_words : 8192; + m->RANGE_FACTS = s->facts ? s->facts : 32; m->JOIN_PATHS = s->paths ? s->paths : 64; m->O3_REGIONS = s->regions ? s->regions : 10; + need(m->O3_REGIONS <= 0x7fffffff / 67, "region reservation is too large"); + need(m->ENUM_MEMBERS <= 0x10000000, "enum member reservation is too large"); + m->cend = m->R_ASM + m->ASM_WORDS + m->NL + m->NL + m->NS + m->EXIT_WORDS; + m->GORG = align_up(m->cend + 256, 256); + } else { + m->GORG = align_up(m->ZFP + 64, 256); + } + m->FBOT = m->GORG + m->glen_need; + m->FTOP = m->FBOT + s->frame; + m->RS0 = m->FTOP; + m->MARK0 = m->FTOP + s->ret; + m->MARKN = s->mark; + m->DSLO = m->MARK0 + m->MARKN; + if (s->words) { + m->MW = s->words; + m->DSHI = m->MW - 16; + if (s->data && m->DSHI - m->DSLO < s->data) die("memory size too small for the data stack"); + } else { + m->DSHI = m->DSLO + s->data; + m->MW = align_up(m->DSHI + 16, 4096); + m->DSHI = m->MW - 16; + } + m->TW = m->MW; + if (s->heap_top) { + m->HEAP_LO = m->MW; + m->HEAP_HI = m->HEAP_LO + s->heap; + m->TW = s->total > m->HEAP_HI ? s->total : align_up(m->HEAP_HI, 4096); + } + check_common(m); + if (s->types) need(m->ZFP == (s->heap_top ? m->CODE_END : m->HEAP_HI) + s->types + s->zfp_pad, "type area"); +} + +static void hexdef(char **p, char *end, const char *n, int v) +{ + int k = snprintf(*p, (size_t)(end - *p), "const %s = 0x%X;\n", n, v); + if (k < 0 || *p + k >= end) die("const block"); + *p += k; +} + +static char *rt_block(const Map *m) +{ + char buf[2048], *p = buf; + hexdef(&p, buf + sizeof buf, "MW", m->MW); + hexdef(&p, buf + sizeof buf, "CODE_END", m->CODE_END); + hexdef(&p, buf + sizeof buf, "HEAP_LO", m->HEAP_LO); + hexdef(&p, buf + sizeof buf, "HEAP_HI", m->HEAP_HI); + hexdef(&p, buf + sizeof buf, "HEAP_GROW", m->GROW); + hexdef(&p, buf + sizeof buf, "ZFP", m->ZFP); + hexdef(&p, buf + sizeof buf, "GORG", m->GORG); + hexdef(&p, buf + sizeof buf, "FBOT", m->FBOT); + hexdef(&p, buf + sizeof buf, "FTOP", m->FTOP); + hexdef(&p, buf + sizeof buf, "RS0", m->RS0); + hexdef(&p, buf + sizeof buf, "MARK0", m->MARK0); + hexdef(&p, buf + sizeof buf, "MARKN", m->MARKN); + hexdef(&p, buf + sizeof buf, "DSLO", m->DSLO); + hexdef(&p, buf + sizeof buf, "DSHI", m->DSHI); + hexdef(&p, buf + sizeof buf, "MAGIC", 0x2B7E1516); + return strdup(buf); +} + +static char *cc_block(const Map *m) +{ + char buf[4096], *p = buf; + hexdef(&p, buf + sizeof buf, "IHN", m->IHN); + hexdef(&p, buf + sizeof buf, "R_IH", m->R_IH); + hexdef(&p, buf + sizeof buf, "R_POOL", m->R_POOL); + hexdef(&p, buf + sizeof buf, "R_POOLEND", m->R_POOLEND); + hexdef(&p, buf + sizeof buf, "NS", m->NS); + hexdef(&p, buf + sizeof buf, "R_SYM", m->R_SYM); + hexdef(&p, buf + sizeof buf, "sname_BASE", m->sname); + hexdef(&p, buf + sizeof buf, "skind_BASE", m->skind); + hexdef(&p, buf + sizeof buf, "sval_BASE", m->sval); + hexdef(&p, buf + sizeof buf, "ssig_BASE", m->ssig); + hexdef(&p, buf + sizeof buf, "sflag_BASE", m->sflag); + hexdef(&p, buf + sizeof buf, "sfix_BASE", m->sfix); + hexdef(&p, buf + sizeof buf, "snext_BASE", m->snext); + hexdef(&p, buf + sizeof buf, "NL", m->NL); + hexdef(&p, buf + sizeof buf, "lname_BASE", m->lname); + hexdef(&p, buf + sizeof buf, "lslot_BASE", m->lslot); + hexdef(&p, buf + sizeof buf, "lhome_BASE", m->lhome); + hexdef(&p, buf + sizeof buf, "lstate_BASE", m->lstate); + hexdef(&p, buf + sizeof buf, "lowner_BASE", m->lowner); + hexdef(&p, buf + sizeof buf, "lused_BASE", m->lused); + hexdef(&p, buf + sizeof buf, "larr_BASE", m->larr); + hexdef(&p, buf + sizeof buf, "lown_BASE", m->lown); + hexdef(&p, buf + sizeof buf, "NF", m->NF); + hexdef(&p, buf + sizeof buf, "fsite_BASE", m->fsite); + hexdef(&p, buf + sizeof buf, "fnext_BASE", m->fnext); + hexdef(&p, buf + sizeof buf, "R_SNAP_BASE", m->R_SNAP); + hexdef(&p, buf + sizeof buf, "R_QBUF_BASE", m->R_QBUF); + hexdef(&p, buf + sizeof buf, "R_SRC_BASE", m->R_SRC); + hexdef(&p, buf + sizeof buf, "R_SEQ_BASE", m->R_SEQ); + hexdef(&p, buf + sizeof buf, "bname_BASE", m->bname); + hexdef(&p, buf + sizeof buf, "bprec_BASE", m->bprec); + hexdef(&p, buf + sizeof buf, "bseq_BASE", m->bseq); + hexdef(&p, buf + sizeof buf, "blen_BASE", m->blen); + hexdef(&p, buf + sizeof buf, "uname_BASE", m->uname); + hexdef(&p, buf + sizeof buf, "useq_BASE", m->useq); + hexdef(&p, buf + sizeof buf, "ulen_BASE", m->ulen); + hexdef(&p, buf + sizeof buf, "aname_BASE", m->aname); + hexdef(&p, buf + sizeof buf, "abin_BASE", m->abin); + hexdef(&p, buf + sizeof buf, "kops_BASE", m->kops); + hexdef(&p, buf + sizeof buf, "R_CTX_BASE", m->R_CTX); + hexdef(&p, buf + sizeof buf, "R_LOOP_BASE", m->R_LOOP); + hexdef(&p, buf + sizeof buf, "R_ASM", m->R_ASM); + hexdef(&p, buf + sizeof buf, "ASM_LABELS", m->ASM_LABELS); + hexdef(&p, buf + sizeof buf, "ASM_REFS", m->ASM_REFS); + hexdef(&p, buf + sizeof buf, "ASM_WORDS", m->ASM_WORDS); + hexdef(&p, buf + sizeof buf, "EXIT_WORDS", m->EXIT_WORDS); + hexdef(&p, buf + sizeof buf, "ENUM_MEMBERS", m->ENUM_MEMBERS); + hexdef(&p, buf + sizeof buf, "TAGGED_ENUMS", m->TAGGED_ENUMS); + hexdef(&p, buf + sizeof buf, "STRUCT_METHODS", m->STRUCT_METHODS); + hexdef(&p, buf + sizeof buf, "GENERATED_WORDS", m->GENERATED_WORDS); + hexdef(&p, buf + sizeof buf, "RANGE_FACTS", m->RANGE_FACTS); + hexdef(&p, buf + sizeof buf, "JOIN_PATHS", m->JOIN_PATHS); + hexdef(&p, buf + sizeof buf, "O3_REGIONS", m->O3_REGIONS); + return strdup(buf); +} + +static void write_memmap(const char *dir, const Map *m) +{ + char path[512], buf[4096]; + snprintf(path, sizeof path, "%s/memmap.h", dir); + snprintf(buf, sizeof buf, + "/* generated by src/tools/genmap for the %s profile. do not edit. */\n" + "#ifndef MEMMAP_H\n#define MEMMAP_H\n" + "#define PW 0x%X\n#define CODE_END 0x%X\n#define HEAP_LO 0x%X\n#define HEAP_HI 0x%X\n" + "#define HEAP_GROW %d\n" + "#ifndef MW\n#define MW 0x%X\n#endif\n" + "#if MW < HEAP_HI\n#error MW is below the end of the initial heap\n#endif\n" + "#define ZFP 0x%X\n" + "#ifdef GORG\n#if GORG != 0x%X\n#error genome GORG does not match this map\n#endif\n" + "#else\n#define GORG 0x%X\n#endif\n" + "#define FBOT 0x%X\n#define FTOP 0x%X\n#define RS0 0x%X\n" + "#define MARK0 0x%X\n#define MARKN 0x%X\n" + "#define DSLO 0x%X\n#define DSHI 0x%X\n" + "#define DS0 DSHI\n#define SYSGATE (ZFP + 2)\n" + "#define DSLIM (DSLO + 1100)\n#define RSLIM (MARK0 - 8)\n" + "#endif\n", + m->name, m->MW, m->CODE_END, m->HEAP_LO, m->HEAP_HI, m->GROW, m->TW, m->ZFP, m->GORG, m->GORG, + m->FBOT, m->FTOP, m->RS0, m->MARK0, m->MARKN, m->DSLO, m->DSHI); + emit_file(path, buf); +} + +static char *map_txt(const Map *m) +{ + char buf[2048]; + int n; + n = snprintf(buf, sizeof buf, + "profile %s\nMW %d (%lld bytes)\nprogram 0 .. %d\nheap %d .. %d (%d words)\n", + m->name, m->TW, (long long)m->TW * 8, m->CODE_END, m->HEAP_LO, m->HEAP_HI, m->HEAP_HI - m->HEAP_LO); + if (m->GROW) + n += snprintf(buf + n, sizeof buf - n, "fixed map ends at %d; the heap grows up to %d\n", m->MW, m->TW); + if (m->typesz) + n += snprintf(buf + n, sizeof buf - n, "types %d .. %d (%d words)\n", m->ZFP - m->typesz, m->ZFP, m->typesz); + n += snprintf(buf + n, sizeof buf - n, "ZFP %d SYSGATE %d\n", m->ZFP, m->ZFP + 2); + if (m->R_ASM) { + int ltype = m->R_ASM + m->ASM_WORDS; + int rexit = ltype + m->NL + m->NL + m->NS; + n += snprintf(buf + n, sizeof buf - n, + "compiler tables %d .. %d\nltype %d R_EXIT %d .. %d\n", + m->R_IH, m->cend, ltype, rexit, rexit + m->EXIT_WORDS); + } + snprintf(buf + n, sizeof buf - n, + "GORG %d reserved through %d\nframe %d .. %d\nreturn %d .. %d\nmark %d .. %d (%d)\ndata %d .. %d\n", + m->GORG, m->GORG + m->glen_need, m->FBOT, m->FTOP, m->FTOP, m->MARK0, + m->MARK0, m->MARK0 + m->MARKN, m->MARKN, m->DSLO, m->DSHI); + return strdup(buf); +} + +static void write_map_txt(const char *dir, const char *txt) +{ + char path[512]; + snprintf(path, sizeof path, "%s/map.txt", dir); + emit_file(path, txt); + fputs(txt, stderr); +} + +static void mkdir1(const char *dir) +{ +#ifdef _WIN32 + char tmp[512]; + char *p; + snprintf(tmp, sizeof(tmp), "%s", dir); + for (p = tmp; *p; p++) { + if (*p == '/' || *p == '\\') { + char save = *p; + *p = 0; + if (*tmp) _mkdir(tmp); + *p = save; + } + } + if (_mkdir(dir) && errno != EEXIST) { perror(dir); exit(1); } +#else + if (mkdir(dir, 0755) && errno != EEXIST) { perror(dir); exit(1); } +#endif +} + +static char *between(const char *src, const char *begin, const char *end) +{ + const char *a = strstr(src, begin), *b; + char *out; size_t n; + if (!a) return 0; + a += strlen(begin); + b = strstr(a, end); + if (!b || b < a) return 0; + n = (size_t)(b - a); + out = malloc(n + 1); + if (!out) die("oom"); + memcpy(out, a, n); + out[n] = 0; + return out; +} + +static int token_hex(const char *text, const char *name, int *out) +{ + const char *p = text; + size_t n = strlen(name); + while ((p = strstr(p, name))) { + int before_ok = p == text || !(isalnum((unsigned char)p[-1]) || p[-1] == '_'); + int after_ok = !(isalnum((unsigned char)p[n]) || p[n] == '_'); + if (before_ok && after_ok) { + const char *q = p + n; + while (*q == ' ' || *q == '\t') q++; + if (*q == '=') { q++; while (*q == ' ' || *q == '\t') q++; } + if (q[0] == '0' && (q[1] == 'x' || q[1] == 'X')) { + *out = (int)strtol(q, 0, 16); + return 1; + } + if (isdigit((unsigned char)*q)) { + *out = (int)strtol(q, 0, 10); + return 1; + } + } + p += n; + } + return 0; +} + +static void expect_hex(const char *text, const char *where, const char *name, int want) +{ + int got; + if (!token_hex(text, name, &got) || got != want) { + fprintf(stderr, "genmap: %s %s is 0x%X in the layout", where, name, want); + if (token_hex(text, name, &got)) fprintf(stderr, " and 0x%X in the source", got); + fprintf(stderr, "\n"); + exit(1); + } +} + +/* the lfa immediate the checked-in rt.zc carries: the default map's ZFP */ +#define DEFAULT_ZFP 589824 + +static void check_sources(const Map *m) +{ + char lfa_old[64]; + FILE *mf = fopen("memmap.h", "rb"); + const char *mm_path = mf ? (fclose(mf), "memmap.h") : "src/core/memmap.h"; + char *rt = slurp("src/runtime/rt.zc"), *cc = slurp("src/compiler/compiler.zc"), *mm = slurp(mm_path); + char *rtb = between(rt, "// @map-rt", "// @end-map"); + char *ccb = between(cc, "// @map-cc", "// @end-map"); + static const char *rt_names[] = { + "MW", "CODE_END", "HEAP_LO", "HEAP_HI", "HEAP_GROW", "ZFP", "GORG", "FBOT", "FTOP", "RS0", + "MARK0", "MARKN", "DSLO", "DSHI", "MAGIC", 0 + }; + static const char *cc_names[] = { + "IHN", "R_IH", "R_POOL", "R_POOLEND", "NS", "R_SYM", + "sname_BASE", "skind_BASE", "sval_BASE", "ssig_BASE", "sflag_BASE", "sfix_BASE", "snext_BASE", + "NL", "lname_BASE", "lslot_BASE", "lhome_BASE", "lstate_BASE", "lowner_BASE", "lused_BASE", "larr_BASE", "lown_BASE", + "NF", "fsite_BASE", "fnext_BASE", "R_SNAP_BASE", "R_QBUF_BASE", "R_SRC_BASE", "R_SEQ_BASE", + "bname_BASE", "bprec_BASE", "bseq_BASE", "blen_BASE", "uname_BASE", "useq_BASE", "ulen_BASE", "aname_BASE", "abin_BASE", "kops_BASE", + "R_CTX_BASE", "R_LOOP_BASE", "R_ASM", "ASM_LABELS", "ASM_REFS", "ASM_WORDS", "EXIT_WORDS", "ENUM_MEMBERS", "TAGGED_ENUMS", "STRUCT_METHODS", "GENERATED_WORDS", "RANGE_FACTS", "JOIN_PATHS", "O3_REGIONS", 0 + }; + int rt_vals[] = { + m->MW, m->CODE_END, m->HEAP_LO, m->HEAP_HI, m->GROW, m->ZFP, m->GORG, m->FBOT, m->FTOP, m->RS0, + m->MARK0, m->MARKN, m->DSLO, m->DSHI, 0x2B7E1516 + }; + int cc_vals[] = { + m->IHN, m->R_IH, m->R_POOL, m->R_POOLEND, m->NS, m->R_SYM, + m->sname, m->skind, m->sval, m->ssig, m->sflag, m->sfix, m->snext, + m->NL, m->lname, m->lslot, m->lhome, m->lstate, m->lowner, m->lused, m->larr, m->lown, + m->NF, m->fsite, m->fnext, m->R_SNAP, m->R_QBUF, m->R_SRC, m->R_SEQ, + m->bname, m->bprec, m->bseq, m->blen, m->uname, m->useq, m->ulen, m->aname, m->abin, m->kops, + m->R_CTX, m->R_LOOP, m->R_ASM, m->ASM_LABELS, m->ASM_REFS, m->ASM_WORDS, m->EXIT_WORDS, m->ENUM_MEMBERS, m->TAGGED_ENUMS, m->STRUCT_METHODS, m->GENERATED_WORDS, m->RANGE_FACTS, m->JOIN_PATHS, m->O3_REGIONS + }; + int i; + if (!rtb || !ccb) die("map markers missing from the checked-in sources"); + snprintf(lfa_old, sizeof lfa_old, "ld %d", DEFAULT_ZFP); + if (!strstr(rt, lfa_old)) die("src/runtime/rt.zc no longer has the default lfa immediate"); + if (!strstr(cc, "ZFP - 16384")) die("src/compiler/compiler.zc lost its initial type-area formula"); + for (i = 0; rt_names[i]; i++) expect_hex(rtb, "src/runtime/rt.zc", rt_names[i], rt_vals[i]); + for (i = 0; rt_names[i]; i++) { + if (!strcmp(rt_names[i], "MAGIC") || !strcmp(rt_names[i], "HEAP_GROW")) continue; + expect_hex(mm, mm_path, !strcmp(rt_names[i], "MW") ? "PW" : rt_names[i], rt_vals[i]); + } + if (m->R_ASM) for (i = 0; cc_names[i]; i++) expect_hex(ccb, "src/compiler/compiler.zc", cc_names[i], cc_vals[i]); + free(rt); free(cc); free(mm); free(rtb); free(ccb); +} + +static void write_sources(const char *dir, const Spec *s, const Map *m) +{ + char path[512], lfa[64], formula[64], snap[64], quote[64]; + char *rt, *block, *spliced, *done; + rt = slurp("src/runtime/rt.zc"); + block = rt_block(m); + spliced = splice(rt, "// @map-rt", "// @end-map", block); + snprintf(lfa, sizeof lfa, "ld %d", m->ZFP); + { char old[64]; snprintf(old, sizeof old, "ld %d", DEFAULT_ZFP); done = replace_all(spliced, old, lfa); } + snprintf(path, sizeof path, "%s/rt.zc", dir); + emit_file(path, done); + free(rt); free(block); free(spliced); free(done); + if (!s->compiler) return; + { + char *cc = slurp("src/compiler/compiler.zc"); + block = cc_block(m); + spliced = splice(cc, "// @map-cc", "// @end-map", block); + if (s->type_formula) { + snprintf(formula, sizeof formula, "ZFP - %d", s->type_formula); + done = replace_all(spliced, "ZFP - 16384", formula); + free(spliced); spliced = done; + } + snprintf(path, sizeof path, "%s/compiler.zc", dir); + emit_file(path, spliced); + free(cc); free(block); free(spliced); + } +} + +static void usage_status(int status) +{ + fprintf(stderr, + "usage: genmap --preset small|run|default|fixed|lsp [-o DIR] [--check]\n" + " genmap --mode compiler|run [-o DIR] [--words N] [options]\n" + " genmap small|run DIR\n" + "options:\n" + " --code N --total N --heap N --heap-lo N --types N --zfp-pad N --genome N\n" + " --frame N --return N --mark N --data N\n" + " --hash N --pool N --symbols N --locals N --fwd N\n" + " --snap N --quote N --src N --seq N --ctx N --loop N\n" + " --binary N --unary N --assignments N --keywords N\n" + " --asm-labels N --asm-refs N --exit N\n" + " --enum-members N --tagged-enums N --methods N --generated-text N\n" + " --facts N --paths N --regions N\n" + " --snap-cap N --quote-cap N --name LABEL\n" + "--preset default has a growing heap above the stacks; fixed is the old 1,081,344-word map.\n" + "--grow lets a map with a fixed size get bigger when its genome needs more room.\n" + "--mode packs a compiler or a run-only image; spare words go to the data stack.\n" + "--check compares that layout to memmap.h and the checked-in const blocks.\n"); + exit(status); +} + +static void usage(void) { usage_status(1); } + +static int take(int argc, char **argv, int *i) +{ + if (*i + 1 >= argc) usage(); + return parse_num(argv[++*i]); +} + +int main(int argc, char **argv) +{ + Spec s; Map m; + if (argc == 2 && !strcmp(argv[1], "--help")) usage_status(0); + const char *outdir = 0, *preset = 0, *mode = 0; + int check = 0, grow = 0, i, have = 0; + int total = 0, set_total = 0, set_words = 0, set_heap = 0, set_heap_lo = 0, set_types = 0, set_zfp = 0; + int code = 0, binary = 0, unary = 0, assignments = 0, keywords = 0; + int asm_labels = 0, asm_refs = 0, exits = 0; + int enum_members = 0, tagged_enums = 0, struct_methods = 0, generated_words = 0; + int facts = 0, paths = 0, regions = 0; + int set_genome = 0, set_frame = 0, set_ret = 0, set_mark = 0, set_data = 0; + int words = 0, heap = 0, heap_lo = 0, types = 0, zfp_pad = 0; + int genome = 0, frame = 0, ret = 0, mark = 0, data = 0; + int set_ihn = 0, set_pool = 0, set_ns = 0, set_nl = 0, set_nf = 0; + int set_snap = 0, set_qbuf = 0, set_src = 0, set_seq = 0, set_ctx = 0, set_loop = 0; + int set_snap_cap = 0, set_quote_cap = 0; + int ihn = 0, pool = 0, ns = 0, nl = 0, nf = 0, snap = 0, qbuf = 0, srcn = 0, seq = 0, ctx = 0, loop = 0; + int snap_cap = 0, quote_cap = 0, set_name = 0; + const char *s_name = 0; + char *txt; + + if (argc == 3 && argv[1][0] != '-' && (strcmp(argv[1], "small") == 0 || strcmp(argv[1], "run") == 0)) { + preset = argv[1]; + outdir = argv[2]; + } else { + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "--preset") == 0) { if (++i >= argc) usage(); preset = argv[i]; } + else if (strcmp(argv[i], "--mode") == 0) { if (++i >= argc) usage(); mode = argv[i]; } + else if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--out") == 0) { if (++i >= argc) usage(); outdir = argv[i]; } + else if (strcmp(argv[i], "--check") == 0) check = 1; + else if (strcmp(argv[i], "--grow") == 0) grow = 1; + else if (strcmp(argv[i], "--words") == 0) { words = take(argc, argv, &i); set_words = 1; } + else if (strcmp(argv[i], "--total") == 0) { total = take(argc, argv, &i); set_total = 1; } + else if (strcmp(argv[i], "--code") == 0) code = take(argc, argv, &i); + else if (strcmp(argv[i], "--binary") == 0) binary = take(argc, argv, &i); + else if (strcmp(argv[i], "--unary") == 0) unary = take(argc, argv, &i); + else if (strcmp(argv[i], "--assignments") == 0) assignments = take(argc, argv, &i); + else if (strcmp(argv[i], "--keywords") == 0) keywords = take(argc, argv, &i); + else if (strcmp(argv[i], "--asm-labels") == 0) asm_labels = take(argc, argv, &i); + else if (strcmp(argv[i], "--asm-refs") == 0) asm_refs = take(argc, argv, &i); + else if (strcmp(argv[i], "--exit") == 0) exits = take(argc, argv, &i); + else if (strcmp(argv[i], "--enum-members") == 0) enum_members = take(argc, argv, &i); + else if (strcmp(argv[i], "--tagged-enums") == 0) tagged_enums = take(argc, argv, &i); + else if (strcmp(argv[i], "--methods") == 0) struct_methods = take(argc, argv, &i); + else if (strcmp(argv[i], "--generated-text") == 0) generated_words = take(argc, argv, &i); + else if (strcmp(argv[i], "--facts") == 0) facts = take(argc, argv, &i); + else if (strcmp(argv[i], "--paths") == 0) paths = take(argc, argv, &i); + else if (strcmp(argv[i], "--regions") == 0) regions = take(argc, argv, &i); + else if (strcmp(argv[i], "--heap") == 0) { heap = take(argc, argv, &i); set_heap = 1; } + else if (strcmp(argv[i], "--heap-lo") == 0) { heap_lo = take(argc, argv, &i); set_heap_lo = 1; } + else if (strcmp(argv[i], "--types") == 0) { types = take(argc, argv, &i); set_types = 1; } + else if (strcmp(argv[i], "--zfp-pad") == 0) { zfp_pad = take(argc, argv, &i); set_zfp = 1; } + else if (strcmp(argv[i], "--genome") == 0) { genome = take(argc, argv, &i); set_genome = 1; } + else if (strcmp(argv[i], "--frame") == 0) { frame = take(argc, argv, &i); set_frame = 1; } + else if (strcmp(argv[i], "--return") == 0) { ret = take(argc, argv, &i); set_ret = 1; } + else if (strcmp(argv[i], "--mark") == 0) { mark = take(argc, argv, &i); set_mark = 1; } + else if (strcmp(argv[i], "--data") == 0) { data = take(argc, argv, &i); set_data = 1; } + else if (strcmp(argv[i], "--hash") == 0) { ihn = take(argc, argv, &i); set_ihn = 1; } + else if (strcmp(argv[i], "--pool") == 0) { pool = take(argc, argv, &i); set_pool = 1; } + else if (strcmp(argv[i], "--symbols") == 0) { ns = take(argc, argv, &i); set_ns = 1; } + else if (strcmp(argv[i], "--locals") == 0) { nl = take(argc, argv, &i); set_nl = 1; } + else if (strcmp(argv[i], "--fwd") == 0) { nf = take(argc, argv, &i); set_nf = 1; } + else if (strcmp(argv[i], "--snap") == 0) { snap = take(argc, argv, &i); set_snap = 1; } + else if (strcmp(argv[i], "--quote") == 0) { qbuf = take(argc, argv, &i); set_qbuf = 1; } + else if (strcmp(argv[i], "--src") == 0) { srcn = take(argc, argv, &i); set_src = 1; } + else if (strcmp(argv[i], "--seq") == 0) { seq = take(argc, argv, &i); set_seq = 1; } + else if (strcmp(argv[i], "--ctx") == 0) { ctx = take(argc, argv, &i); set_ctx = 1; } + else if (strcmp(argv[i], "--loop") == 0) { loop = take(argc, argv, &i); set_loop = 1; } + else if (strcmp(argv[i], "--snap-cap") == 0) { snap_cap = take(argc, argv, &i); set_snap_cap = 1; } + else if (strcmp(argv[i], "--quote-cap") == 0) { quote_cap = take(argc, argv, &i); set_quote_cap = 1; } + else if (strcmp(argv[i], "--name") == 0) { if (++i >= argc) usage(); s_name = argv[i]; set_name = 1; } + else usage(); + } + } + if (preset && mode) die("pass --preset or --mode, not both"); + if (!preset && !mode) usage(); + if (preset) { + if (strcmp(preset, "small") == 0) preset_small(&s); + else if (strcmp(preset, "run") == 0) preset_run(&s); + else if (strcmp(preset, "default") == 0) preset_default(&s); + else if (strcmp(preset, "lsp") == 0) preset_lsp(&s); + else if (strcmp(preset, "fixed") == 0) preset_fixed(&s); + else die("preset is small, run, default, fixed or lsp"); + } else { + if (strcmp(mode, "compiler") == 0) mode_defaults(&s, 1); + else if (strcmp(mode, "run") == 0) mode_defaults(&s, 0); + else die("mode is compiler or run"); + } + if (set_words) s.words = words; + if (set_total) s.total = total; + if (set_heap) s.heap = heap; + if (set_heap_lo) { s.heap_lo = heap_lo; if (s.heap_top) s.code_end = heap_lo; } + if (code) { if (s.heap_top) s.code_end = code; else s.heap_lo = code; } + s.binary = binary; s.unary = unary; s.assignments = assignments; s.keywords = keywords; + s.asm_labels = asm_labels; s.asm_refs = asm_refs; s.exits = exits; + s.enum_members = enum_members; s.tagged_enums = tagged_enums; s.struct_methods = struct_methods; s.generated_words = generated_words; + s.facts = facts; s.paths = paths; s.regions = regions; + if (set_types) { s.types = types; s.type_formula = types == 16384 ? 0 : types; } + if (set_zfp) s.zfp_pad = zfp_pad; + if (set_genome) s.genome = genome; + if (set_frame) s.frame = frame; + if (set_ret) s.ret = ret; + if (set_mark) s.mark = mark; + if (set_data) s.data = data; + if (set_ihn) s.ihn = ihn; + if (set_pool) s.pool = pool; + if (set_ns) s.ns = ns; + if (set_nl) s.nl = nl; + if (set_nf) s.nf = nf; + if (set_snap) s.snap = snap; + if (set_qbuf) s.qbuf = qbuf; + if (set_src) s.src = srcn; + if (set_seq) s.seq = seq; + if (set_ctx) s.ctx = ctx; + if (set_loop) s.loop = loop; + if (set_snap_cap) s.snap = snap_cap; + if (set_quote_cap) { if (quote_cap > 0x7fffffff / 3) die("quote size overflow"); s.qbuf = quote_cap * 3; } + if (set_name) s.name = s_name; + /* --grow: a preset with a fixed memory size gets as many words as its layout needs. the + fixed size stays when it is big enough, so a program that fits builds the same map as + before and a bigger genome moves the top of memory up instead of failing */ + if (grow && s.words) { + Spec t = s; Map probe; + t.words = 0; + if (!t.data) t.data = 16368; + layout(&t, &probe); + if (probe.MW > s.words) s.words = probe.MW; + } + if (s.compiler && (!s.ihn || !s.pool || !s.ns || !s.nl || !s.nf)) die("compiler tables are empty"); + layout(&s, &m); + txt = map_txt(&m); + if (check) check_sources(&m); + if (outdir) { + mkdir1(outdir); + write_memmap(outdir, &m); + write_map_txt(outdir, txt); + write_sources(outdir, &s, &m); + have = 1; + } + if (!have && !check) usage(); + if (check && !outdir) fputs(txt, stdout); + free(txt); + return 0; +} diff --git a/src/tools/mkgenome.c b/src/tools/mkgenome.c new file mode 100644 index 0000000..bb2e787 --- /dev/null +++ b/src/tools/mkgenome.c @@ -0,0 +1,31 @@ +/* turns a genome image (little-endian 64-bit words on stdin, as written by + src/tools/build.zc) into genome.h */ +#include <stdio.h> +#include <stdint.h> +#ifdef _WIN32 +#include <fcntl.h> +#include <io.h> +#endif +#include "memmap.h" +int main(void) +{ +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + unsigned char b[8]; int n = 0; size_t got; + printf("#define GORG %d\nstatic const int64_t genome[] = {", GORG); + while ((got = fread(b, 1, 8, stdin)) == 8) { + uint64_t u = (uint64_t)b[0] | (uint64_t)b[1] << 8 | (uint64_t)b[2] << 16 | (uint64_t)b[3] << 24 + | (uint64_t)b[4] << 32 | (uint64_t)b[5] << 40 | (uint64_t)b[6] << 48 | (uint64_t)b[7] << 56; + if ((n == 0 && u != (uint64_t)FTOP * 2 + 1) || + (n == 1 && u != (uint64_t)ZFP * 2 + 1) || (n == 2 && u != 4)) { + fprintf(stderr, "mkgenome: input is not a genome for this map\n"); return 1; + } + printf("%s%lld", !n ? "\n" : n % 12 ? "," : ",\n", (long long)u); + n++; + } + if (got || ferror(stdin) || n < 7) { fprintf(stderr, "mkgenome: incomplete genome\n"); return 1; } + printf("\n};\n#define GLEN %d\n", n); + return n ? 0 : 1; +} diff --git a/src/tools/mklsp.sh b/src/tools/mklsp.sh new file mode 100755 index 0000000..5edf14a --- /dev/null +++ b/src/tools/mklsp.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# builds the language server: src/tools/mklsp.sh [outdir] leaves outdir/zclsp. +# the server is the real compiler (src/compiler/compiler.zc) in check mode plus +# src/lsp/server.zc. its memory map is sized to that program by src/tools/automap.sh, +# so the genome slot is as big as it needs to be and no bigger. +set -e +cd "$(dirname "$0")/../.." +O=${1:-build/lsp}; CC=${CC:-gcc} +# DOCS=1: the image carries the doc comments, so hover can show what a name is for +DOCS=1 bash src/tools/automap.sh lsp "$O" src/lsp/server.zc >/dev/null +rm -rf "$O/src"; mkdir -p "$O/src" +cp src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$O/memmap.h" "$O/src/" +cp "$O/genome.h" "$O/src/lsp_genome.h" +$CC -O2 -w -fwrapv -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME "$O/src/core.c" "$O/src/io_linux.c" -I"$O/src" -o "$O/zclsp" diff --git a/src/tools/mklspwasm.sh b/src/tools/mklspwasm.sh new file mode 100755 index 0000000..56ba43b --- /dev/null +++ b/src/tools/mklspwasm.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# builds the language server as WebAssembly: editors/vscode/server/lsp.wasm by +# default, or the path given as the first argument. the server is the compiler +# and src/lsp/server.zc on the map and genome that src/tools/mklsp.sh made in build/lsp, +# and the microcore (src/micro/micro.c) runs them. the host is client/wasmproc.js. +# needs clang with the wasm32 target and wasm-ld; run src/tools/mklsp.sh first. +# src/tools/mklspwasm.sh [outfile.wasm] +set -e +cd "$(dirname "$0")/../.." +OUT=${1:-editors/vscode/server/lsp.wasm} +LSP_BUILD_DIR=${LSP_BUILD_DIR:-build/lsp} +if [ "${LSP_REUSE_BUILD:-0}" != 1 ]; then bash src/tools/mklsp.sh "$LSP_BUILD_DIR" >/dev/null; fi +[ -f "$LSP_BUILD_DIR/src/lsp_genome.h" ] || { echo "mklspwasm: no language server image in $LSP_BUILD_DIR" >&2; exit 1; } +clang --target=wasm32 -O2 -w -fwrapv -nostdlib -fno-builtin -mbulk-memory -DJTYPE=int -DUSE_LSP_GENOME \ + -I"$LSP_BUILD_DIR/src" -Isrc/micro src/micro/micro.c src/micro/port_wasm_stream.c \ + -Wl,--no-entry -Wl,--export=wasm_chunk -Wl,--export=wasm_chunk_cap -Wl,--export=wasm_feed \ + -Wl,--export=wasm_close -Wl,--export=wasm_start -Wl,--export=wasm_resume \ + -Wl,--export=memory -Wl,--allow-undefined -Wl,--initial-memory=41943040 -Wl,-z,stack-size=65536 \ + -o "$OUT" +ls -l "$OUT" diff --git a/src/tools/mkvsix.sh b/src/tools/mkvsix.sh new file mode 100755 index 0000000..5ca8ef5 --- /dev/null +++ b/src/tools/mkvsix.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# builds the vs code extension, with the language server inside it: the tinyvm +# javascript vm (src/js/tinyvm.js, compiled from src/js/tinyvm.ts) and the server's image +# (src/js/lsp.json, from build/lsp/genome.h and its map), and the wasm server +# (src/tools/mklspwasm.sh) when clang can build it. needs node and tsc; the +# server build (src/tools/mklsp.sh) runs first if build/lsp is missing. +# src/tools/mkvsix.sh [outfile.vsix] +set -e +cd "$(dirname "$0")/../.." +OUT=${1:-$PWD/build/zc-language-support.vsix} +case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac +mkdir -p "$(dirname "$OUT")" +bash src/tools/mklsp.sh build/lsp >/dev/null +# typescript errors stop packaging, so broken generated code is never shipped. +command -v tsc >/dev/null || { echo "mkvsix: tsc is not installed" >&2; exit 1; } +tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir src/js +node src/js/extract_lsp.js +mkdir -p editors/vscode/server +cp src/js/tinyvm.js src/js/layout.js src/js/lsp.json editors/vscode/server/ +# the wasm server runs when it is there; without clang the vm server is packed alone +if command -v clang >/dev/null && clang --print-targets | grep -q wasm32; then + LSP_BUILD_DIR=build/lsp LSP_REUSE_BUILD=1 bash src/tools/mklspwasm.sh editors/vscode/server/lsp.wasm +else + rm -f editors/vscode/server/lsp.wasm + echo "no clang with wasm32: packing the javascript vm server only" >&2 +fi +cd editors/vscode +if [ -n "${VSCE:-}" ]; then + "$VSCE" package --allow-missing-repository --skip-license -o "$OUT" >/dev/null +elif command -v vsce >/dev/null; then + vsce package --allow-missing-repository --skip-license -o "$OUT" >/dev/null +else + npx -y @vscode/vsce package --allow-missing-repository --skip-license -o "$OUT" >/dev/null +fi +echo "$OUT" diff --git a/src/tools/zc0.c b/src/tools/zc0.c new file mode 100644 index 0000000..5ef5737 --- /dev/null +++ b/src/tools/zc0.c @@ -0,0 +1,880 @@ +/* zc0: stage 0 of the bootstrap. a small c compiler for the subset of zc that + compiler.zc is written in. it reads zc source on stdin and writes a genome + header (vm words placed at GORG) on stdout, in the same image layout the + resident compiler's image mode produces. no lifetime analysis, no quote, no + parse words, no block expressions: those only exist in compiler.zc. tags + (m, d, -> u) and casts (as u, as d) are parsed and recorded in signatures + but not checked. */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <ctype.h> +#ifdef _WIN32 +#include <fcntl.h> +#include <io.h> +#endif +#include "memmap.h" +#include "fops.h" + +#ifndef MAXW +#define MAXW 0x80000 +#endif + +enum { T_EOF, T_NUM, T_ID, T_STR, T_OP, T_FLT, T_BSTR }; +enum { K_FN = 1, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE, K_DOC }; +enum { O_Q, O_LD, O_ST, O_PICK, O_DROP, O_SWAP, O_CALL, O_JMP, O_JZ, O_RET, + O_IN, O_EMPTY, O_OUT, O_ADD, O_SUB, O_MUL, O_AND, O_OR, O_XOR, O_SHR, O_LT }; +static const char *mn = "q@!$_~cjzr,?.+-*&|^><%"; + +static char *src; static int pos, line = 1, tstart, hdr_start; +/* the signature of the function being defined: its header's source text, for the language server (ZC0DOCS) */ +static char sgtext[400], sgname[80]; +static int tk, glue; static int64_t tv; static char ts[4096]; /* current token */ +static int xf, xs, lvi; /* float, byte string; the plain local it came from, or -1 */ + +static int64_t *img; static int imgcap, here = GORG; +static void emit(int64_t w) { + if (!img || here - GORG >= imgcap) { + int capacity; int64_t *next; + if (imgcap > (0x7fffffff - GORG) / 2) { fprintf(stderr, "zc0: image address overflow\n"); exit(1); } + capacity = imgcap ? imgcap * 2 : MAXW; + if (capacity <= imgcap || capacity > 0x7fffffff - GORG) { fprintf(stderr, "zc0: image address overflow\n"); exit(1); } + next = realloc(img, (size_t)capacity * sizeof *img); + if (!next) { fprintf(stderr, "zc0: cannot grow image buffer\n"); exit(1); } + img = next; imgcap = capacity; + } + img[here++ - GORG] = w; +} +static void lit(int64_t v) { + if ((v >> 62) != 0 && (v >> 62) != -1) { + emit((v >> 32) * 2 + 1); emit(0x100000000LL * 2 + 1); emit(O_MUL * 2); + emit((int64_t)((uint64_t)v & 0xffffffffu) * 2 + 1); emit(O_OR * 2); + } else emit(v * 2 + 1); +} +static void op(int k) { emit((int64_t)k * 2); } +static void setlit(int at, int64_t v) { img[at - GORG] = v * 2 + 1; } + +static int pguess, pbad; /* inside a speculative type parse: an error is noted instead of stopping */ +static void die(const char *m) { if (pguess) { pbad = 1; return; } fprintf(stderr, "zc0: line %d: %s (at '%s')\n", line, m, ts); exit(1); } + +/* ---- lexer ---- */ +static const char *ops3[] = { "<<=", ">>=", 0 }; +static const char *ops2[] = { "==", "!=", "<=", ">=", "<<", ">>", "&&", "||", "++", "--", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "->", 0 }; +static int esc(void) +{ + int c = src[pos++]; + if (c != '\\') return c; + c = src[pos++]; + switch (c) { case 'n': return 10; case 't': return 9; case 'r': return 13; case '0': return 0; case 'e': return 27; } + return c; +} +/* doc comments, as the self-hosted compiler reads them with docs on: the // + lines directly above a declaration, up to a blank line or any other text */ +static char dbuf[2100], dsnap[2100], dnm[64]; static int dn, dlast, dline, dsn, docpending; +static void doccomment(void) /* pos is on the first slash */ +{ + int cl = line, k = pos - 1, bol; + while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--; + bol = k < 0 || src[k] == '\n'; + pos += 2; + if (!bol) { while (src[pos] && src[pos] != '\n') pos++; return; } + if (dn && dlast != cl - 1) dn = 0; + if (src[pos] == ' ') pos++; + while (src[pos] && src[pos] != '\n') { if (dn < 2040) dbuf[dn++] = src[pos]; pos++; } + while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r')) dn--; + dbuf[dn++] = '\n'; + dlast = cl; docpending = 1; +} +/* block comments may document a declaration on the same line */ +static void docblock(void) +{ + int cl = line, k = pos - 1, bol, start = 1, c; + while (k >= 0 && (src[k] == ' ' || src[k] == '\t')) k--; + bol = k < 0 || src[k] == '\n'; + if (!bol || (dn && dlast != cl - 1)) dn = 0; + pos += 2; + if (src[pos] == '*') { pos++; if (src[pos] == '/') { pos++; return; } } + while (src[pos]) { + if (start) { + while (src[pos] == ' ' || src[pos] == '\t') pos++; + if (src[pos] == '*') { + pos++; + if (src[pos] == '/') { pos++; break; } + if (src[pos] == ' ') pos++; + } + start = 0; + } + c = src[pos++]; + if (c == '*' && src[pos] == '/') { pos++; break; } + if (dn < 2040) dbuf[dn++] = (char)c; + if (c == '\n') { line++; start = 1; } + } + while (dn && (dbuf[dn - 1] == ' ' || dbuf[dn - 1] == '\t' || dbuf[dn - 1] == '\r' || dbuf[dn - 1] == '\n')) dn--; + dbuf[dn++] = '\n'; dlast = line; docpending = 1; +} +static void next(void) +{ + int ws = 0, i; + for (;;) { + while (src[pos] && isspace((unsigned char)src[pos])) { if (src[pos] == '\n') line++; pos++; ws = 1; } + if (src[pos] == '/' && src[pos + 1] == '/') { doccomment(); ws = 1; continue; } + if (src[pos] == '/' && src[pos + 1] == '*') { docblock(); ws = 1; continue; } + break; + } + glue = !ws; + tstart = pos; + if (docpending) { + dline = 0; + if (dn && (line == dlast || line == dlast + 1)) dline = line; + docpending = 0; + } + if (!src[pos] || src[pos] == '`') { tk = T_EOF; strcpy(ts, "<eof>"); return; } + if (isdigit((unsigned char)src[pos]) || (src[pos] == '.' && isdigit((unsigned char)src[pos + 1]))) { + int p = pos, fl = 0; + if (src[pos] == '0' && (src[pos + 1] == 'x' || src[pos + 1] == 'X')) { + char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; + } + if (src[p] == '.') { + int nch = (unsigned char)src[p + 1]; + if (isdigit(nch) || nch == 'e' || nch == 'E' || !(isalpha(nch) || nch == '_')) fl = 1; + } else { + while (isdigit((unsigned char)src[p])) p++; + if (src[p] == '.') { + int nch = (unsigned char)src[p + 1]; + /* 1.5 and 1. and 1.e2 are floats. 1.field is an integer and a field. */ + if (isdigit(nch) || nch == 'e' || nch == 'E' || !(isalpha(nch) || nch == '_')) fl = 1; + } + } + if (!fl && (src[p] == 'e' || src[p] == 'E')) { + int q = p + 1; if (src[q] == '+' || src[q] == '-') q++; if (isdigit((unsigned char)src[q])) fl = 1; + } + if (fl) { + char b[80]; int n = 0, frac = 0; + while (src[pos] == '.' || isdigit((unsigned char)src[pos])) { + if (src[pos] == '.') { if (frac) break; frac = 1; } + if (n < 78) b[n++] = src[pos]; + pos++; + } + if (src[pos] == 'e' || src[pos] == 'E') { + int q = pos + 1; if (src[q] == '+' || src[q] == '-') q++; + if (isdigit((unsigned char)src[q])) { + if (n < 78) b[n++] = src[pos++]; + if (src[pos] == '+' || src[pos] == '-') { if (n < 78) b[n++] = src[pos++]; } + while (isdigit((unsigned char)src[pos]) && n < 78) b[n++] = src[pos++]; + } + } + b[n] = 0; memcpy(ts, b, (size_t)n + 1); tv = (int64_t)fop_parse(b); tk = T_FLT; return; + } + { char *e; tv = (int64_t)strtoll(src + pos, &e, 0); memcpy(ts, src + pos, e - src - pos); ts[e - src - pos] = 0; pos = e - src; tk = T_NUM; return; } + } + if (isalpha((unsigned char)src[pos]) || src[pos] == '_') { + i = 0; while (isalnum((unsigned char)src[pos]) || src[pos] == '_') ts[i++] = src[pos++]; + ts[i] = 0; + if (i == 1 && ts[0] == 'b' && src[pos] == '"') { + pos++; i = 0; + while (src[pos] && src[pos] != '"' && i < 4095) ts[i++] = (char)esc(); + ts[i] = 0; if (src[pos] == '"') pos++; tk = T_BSTR; return; + } + tk = T_ID; return; + } + if (src[pos] == '\'') { pos++; tv = esc(); if (src[pos++] != '\'') die("bad char literal"); tk = T_NUM; strcpy(ts, "'c'"); return; } + if (src[pos] == '"') { pos++; i = 0; while (src[pos] && src[pos] != '"') ts[i++] = (char)esc(); ts[i] = 0; pos++; tk = T_STR; return; } + tk = T_OP; + for (i = 0; ops3[i]; i++) if (!strncmp(src + pos, ops3[i], 3)) { strcpy(ts, ops3[i]); pos += 3; return; } + for (i = 0; ops2[i]; i++) if (!strncmp(src + pos, ops2[i], 2)) { strcpy(ts, ops2[i]); pos += 2; return; } + ts[0] = src[pos++]; ts[1] = 0; +} +typedef struct { char *src; int pos, line, tk, glue; int64_t tv; char ts[4096]; int xf, xs; } Lex; +static void lexsave(Lex *L) { L->src = src; L->pos = pos; L->line = line; L->tk = tk; L->glue = glue; L->tv = tv; memcpy(L->ts, ts, sizeof ts); L->xf = xf; L->xs = xs; } +static void lexrest(Lex *L) { src = L->src; pos = L->pos; line = L->line; tk = L->tk; glue = L->glue; tv = L->tv; memcpy(ts, L->ts, sizeof ts); xf = L->xf; xs = L->xs; } +static int is(const char *s) { return (tk == T_OP || tk == T_ID) && !strcmp(ts, s); } +static int accept(const char *s) { if (is(s)) { next(); return 1; } return 0; } +static void expect(const char *s) { if (!accept(s)) { char b[64]; sprintf(b, "expected '%s'", s); die(b); } } +static char *ident(void) { static char b[8][256]; static int k; char *r = b[k++ & 7]; if (tk != T_ID) die("expected a name"); strcpy(r, ts); next(); return r; } + +/* ---- symbols ---- */ +typedef struct { char name[64]; int kind, sig, defd; int64_t val; } Sym; +static Sym sy[16384]; static int nsy; +typedef struct { int at, sym; } Fix; +static Fix fx[65536]; static int nfx; +static Sym *find(const char *n) { int i; for (i = nsy; i--;) if (!strcmp(sy[i].name, n)) return &sy[i]; return 0; } +static Sym *add(const char *n, int kind, int64_t val) +{ + Sym *s = find(n); + if (s && s->kind == K_FN && kind == K_FN && !s->defd) { s->val = val; return s; } /* prototype or forward use */ + s = &sy[nsy++]; strncpy(s->name, n, 63); s->kind = kind; s->val = val; s->defd = 1; s->sig = 0; return s; +} +static void fnref(const char *n) /* push a function address, fixing it up later if needed */ +{ + Sym *s = find(n); + if (!s) { s = add(n, K_FN, 0); s->defd = 0; } + if (s->kind != K_FN) die("not a function"); + if (s->defd) { lit(s->val); return; } + fx[nfx].at = here; fx[nfx++].sym = (int)(s - sy); lit(0); +} + +/* ---- locals ---- */ +typedef struct { char name[64]; int slot, arr, flt, str; } Loc; +static Loc lo[1024]; static int nlo, nslot, infn; +static Loc *findl(const char *n) { int i; for (i = nlo; i--;) if (!strcmp(lo[i].name, n)) return &lo[i]; return 0; } +static void laddr(int k) { lit(ZFP); op(O_LD); if (k) { lit(k); op(O_ADD); } } + +/* ---- operator tables ---- */ +static void seq_isz(void) { lit(0); op(O_PICK); lit(0); op(O_SWAP); op(O_SUB); op(O_OR); lit(63); op(O_SHR); lit(1); op(O_ADD); } +/* ±0 is false. a float becomes 0 or 1 before the integer test. */ +static void ftest(void) { if (!xf) return; emit(1200); emit(0); op(605); lit(1); op(18); xf = 0; } +static void seq_nz(void) { lit(0); op(O_PICK); lit(0); op(O_SWAP); op(O_SUB); op(O_OR); lit(63); op(O_SHR); lit(0); op(O_SWAP); op(O_SUB); } +static int binprec(void) +{ + static const char *n[] = { "*", "/", "%", "+", "-", "<<", ">>", "<", ">", "<=", ">=", "==", "!=", "&", "^", "|", "&&", "||", 0 }; + static int p[] = { 10, 10, 10, 9, 9, 8, 8, 7, 7, 7, 7, 6, 6, 5, 4, 3, 2, 1 }; + int i; + if (tk != T_OP) return 0; + for (i = 0; n[i]; i++) if (!strcmp(ts, n[i])) return p[i]; + return 0; +} +static int fis(const char *o) +{ + return !strcmp(o, "+") || !strcmp(o, "-") || !strcmp(o, "*") || !strcmp(o, "/") + || !strcmp(o, "<") || !strcmp(o, ">") || !strcmp(o, "<=") || !strcmp(o, ">=") + || !strcmp(o, "==") || !strcmp(o, "!="); +} +static void fbinop(const char *o, int lf, int rf) +{ + if (!lf) { op(O_SWAP); op(608); op(O_SWAP); } + if (!rf) op(608); + if (!strcmp(o, "+")) { op(601); xf = 1; } + else if (!strcmp(o, "-")) { op(602); xf = 1; } + else if (!strcmp(o, "*")) { op(603); xf = 1; } + else if (!strcmp(o, "/")) { op(604); xf = 1; } + else if (!strcmp(o, "==")) { op(605); xf = 0; } + else if (!strcmp(o, "!=")) { op(605); lit(1); op(18); xf = 0; } + else if (!strcmp(o, "<")) { op(606); xf = 0; } + else if (!strcmp(o, ">")) { op(O_SWAP); op(606); xf = 0; } + else if (!strcmp(o, "<=")) { op(607); xf = 0; } + else if (!strcmp(o, ">=")) { op(O_SWAP); op(607); xf = 0; } + else xf = 0; +} +static int sis(const char *o) +{ + return !strcmp(o, "+") || !strcmp(o, "<") || !strcmp(o, ">") || !strcmp(o, "<=") + || !strcmp(o, ">=") || !strcmp(o, "==") || !strcmp(o, "!="); +} +static void sbinop(const char *o) +{ + if (!strcmp(o, "+")) { fnref("cat_s"); op(O_CALL); xs = 1; } + else if (!strcmp(o, "==")) { fnref("eq_s"); op(O_CALL); xs = 0; } + else if (!strcmp(o, "!=")) { fnref("eq_s"); op(O_CALL); lit(1); op(O_XOR); xs = 0; } + else if (!strcmp(o, "<")) { fnref("lt_s"); op(O_CALL); xs = 0; } + else if (!strcmp(o, ">")) { op(O_SWAP); fnref("lt_s"); op(O_CALL); xs = 0; } + else if (!strcmp(o, "<=")) { fnref("le_s"); op(O_CALL); xs = 0; } + else { op(O_SWAP); fnref("le_s"); op(O_CALL); xs = 0; } + xf = 0; +} +static void binop(const char *o) +{ + if (!strcmp(o, "*")) op(O_MUL); + else if (!strcmp(o, "/")) { fnref("zdiv"); op(O_CALL); } + else if (!strcmp(o, "%")) { fnref("zmod"); op(O_CALL); } + else if (!strcmp(o, "+")) op(O_ADD); + else if (!strcmp(o, "-")) op(O_SUB); + else if (!strcmp(o, "<<")) { fnref("zshl"); op(O_CALL); } + else if (!strcmp(o, ">>")) op(O_SHR); + else if (!strcmp(o, "<")) op(O_LT); + else if (!strcmp(o, ">")) { op(O_SWAP); op(O_LT); } + else if (!strcmp(o, "<=")) { op(O_SWAP); op(O_LT); lit(1); op(O_XOR); } + else if (!strcmp(o, ">=")) { op(O_LT); lit(1); op(O_XOR); } + else if (!strcmp(o, "==")) { op(O_SUB); seq_isz(); } + else if (!strcmp(o, "!=")) { op(O_SUB); seq_nz(); } + else if (!strcmp(o, "&")) op(O_AND); + else if (!strcmp(o, "^")) op(O_XOR); + else if (!strcmp(o, "|")) op(O_OR); + else die("bad operator"); +} + +/* ---- constant expressions (globals, array sizes, const) ---- */ +static int64_t cexpr(void); +static int64_t cprim(void) +{ + int64_t v; Sym *s; + if (tk == T_NUM) { v = tv; next(); return v; } + if (accept("-")) return -cprim(); + if (accept("~")) return ~cprim(); + if (accept("(")) { v = cexpr(); expect(")"); return v; } + if (tk == T_ID && (s = find(ts)) && (s->kind == K_CONST || (s->kind != K_GVAR && s->defd))) { next(); return s->val; } + die("constant expected"); return 0; +} +static int64_t cexpr(void) +{ + int64_t v = cprim(); + for (;;) { + if (accept("+")) v += cprim(); else if (accept("-")) v -= cprim(); + else if (accept("*")) v *= cprim(); else if (accept("|")) v |= cprim(); + else if (accept("&")) v &= cprim(); else if (accept("<<")) v <<= cprim(); + else if (accept(">>")) v >>= cprim(); else return v; + } +} + +/* ---- types ---- + zc0 has no types: a type is read and thrown away. all it has to know is the + shape, so that [n]T still reserves n words and the rest reserve one. the + grammar is the compiler's own (see ptype in compiler.zc): + ptype a type, then any number of (params): int(int, int) is a function type + ptype0 a type with its stars: int*, vec<T>*, point** + ptype1 a base: [n]T, []T, fixed []T, fn, a name, or a generic instance + each returns the words a variable of the type takes: n for a fixed array, 1 + for a growable handle or anything scalar. */ +static int ptype0(void); +static int ptype(void); +static int ptype1(void) +{ + int n, e; + if (accept("[")) { + if (accept("]")) { ptype0(); return 1; } /* []T is a handle */ + n = (int)cexpr(); expect("]"); + e = ptype0(); + return n > 0 ? n * e : e; + } + if (is("fixed")) { next(); return ptype0(); } + if (is("fn")) { next(); return 1; } /* fn(int): the parameters follow, in ptype */ + if (tk != T_ID) { die("a type name"); return 1; } + ident(); + if (accept("<")) { /* generic arguments: vec<node*> */ + do { ptype(); } while (accept(",")); + if (is(">>")) ts[1] = 0; /* >> closes two lists: leave the second > */ + else expect(">"); + } + return 1; +} +static int ptype0(void) +{ + int n = ptype1(); + while (accept("*")) n = 1; + return n; +} +static int ptype(void) +{ + int n = ptype0(); + while (accept("(")) { + if (!accept(")")) { do { ptype(); } while (accept(",")); expect(")"); } + n = 1; + } + return n; +} +/* the ownership tag after a type, .u .m or .d: 0 when there is none */ +static int ptag; +static void ptagopt(void) +{ + ptag = 0; + if (!accept(".")) return; + if (is("m")) ptag = 1; else if (is("d")) ptag = 2; else if (is("u")) ptag = 3; else die("a tag is .u, .m or .d"); + next(); +} +/* an optional type in front of a name. the words it needs, or -1 when there is + no type here at all, which is how an untyped name is told from a type. the + parse is taken back when what follows is not a name. */ +/* words that open a statement or an expression, so a type never starts with + one of them. they are all plain identifiers to the lexer. */ +static int notatype(void) +{ + static const char *w[] = { "return", "if", "else", "while", "for", "do", "break", "continue", + "let", "var", "fn", "const", "enhance", "struct", "enum", "asm", "switch", "case", + "default", "immediate", "type", "include", "as", "in", "new", "sizeof", "typeof", + "o3", "unchecked", 0 }; + int i; + if (tk != T_ID) return 0; + for (i = 0; w[i]; i++) if (!strcmp(ts, w[i])) return 1; + return 0; +} +static int optype(void) +{ + Lex L; int n; + if (notatype() || (tk != T_ID && !is("["))) return -1; + lexsave(&L); pguess = 1; pbad = 0; n = ptype(); ptagopt(); pguess = 0; + if (tk == T_ID && !pbad) return n; + lexrest(&L); + return -1; +} +/* a name followed by ( : a function definition rather than a variable */ +static int isfn(void) +{ + Lex L; int r; + lexsave(&L); pguess = 1; + r = tk == T_ID; + if (r) { ident(); r = is("("); } + pguess = 0; lexrest(&L); + return r; +} + +/* ---- expressions ---- */ +static int lv; /* the stack top is an address not yet loaded */ +static void rval(void) { if (lv) { op(O_LD); lv = 0; } } +static void expr(void); +static void strlit(void) /* inline word string: jump over it, push its address */ +{ + int j = here, a, i, n = (int)strlen(ts); + lit(0); op(O_JMP); a = here; + for (i = 0; i < n; i++) emit((unsigned char)ts[i]); + emit(0); setlit(j, here); lit(a); next(); +} +/* b"...": length word, then bytes packed 8 per word, little-endian, plus a 0 */ +static void bstrlit(void) +{ + int j = here, a, i = 0, k, n = (int)strlen(ts); + uint64_t w; + lit(0); op(O_JMP); a = here; + emit(n); + while (i < n) { + w = 0; k = 0; + while (k < 8 && i < n) { w |= (uint64_t)(unsigned char)ts[i] << (k * 8); k++; i++; } + emit((int64_t)w); + } + if ((n & 7) == 0) emit(0); + setlit(j, here); lit(a); next(); +} +static int returns_bstr(const char *n) +{ + return !strcmp(n, "cat_s") || !strcmp(n, "slice_s") || !strcmp(n, "n_to_s") || !strcmp(n, "new_s"); +} +static int args(void) { int n = 0; expect("("); if (!accept(")")) { do { expr(); rval(); n++; } while (accept(",")); expect(")"); } return n; } +static void asmblock(void); +static void primary(void) +{ + char n[64]; Loc *l; Sym *s; + lv = 0; lvi = -1; xf = 0; xs = 0; + if (tk == T_FLT) { emit(1200); emit(tv); xf = 1; next(); return; } + if (tk == T_NUM) { lit(tv); next(); return; } + if (tk == T_BSTR) { bstrlit(); xs = 1; return; } + if (tk == T_STR) { strlit(); return; } + if (accept("(")) { expr(); expect(")"); return; } + if (is("asm")) { next(); asmblock(); return; } + strcpy(n, ident()); + if ((l = findl(n))) { laddr(l->slot); lv = !l->arr; if (!l->arr) { xf = l->flt; xs = l->str; lvi = (int)(l - lo); } return; } + s = find(n); + if (s && s->kind == K_CONST) { lit(s->val); return; } + if (s && s->kind == K_GVAR) { lit(s->val); lv = 1; return; } + if (s && s->kind == K_GARR) { lit(s->val); return; } + if (is("(")) { + int na = args(); + if (!strcmp(n, "put_s") && na == 1 && xs) fnref("put_bs"); + else if (!strcmp(n, "len")) fnref("len_s"); + else fnref(n); + op(O_CALL); + xs = returns_bstr(n); xf = 0; + return; + } + fnref(n); +} +static void incdec(int post, int d) +{ + if (!lv) die("++/-- needs an lvalue"); + lv = 0; + if (lvi >= 0) lo[lvi].str = 0; + xs = 0; + if (post) { lit(0); op(O_PICK); op(O_LD); lit(0); op(O_PICK); lit(d); op(O_ADD); lit(2); op(O_PICK); op(O_ST); op(O_SWAP); op(O_DROP); } + else { lit(0); op(O_PICK); op(O_LD); lit(d); op(O_ADD); lit(0); op(O_PICK); lit(2); op(O_PICK); op(O_ST); op(O_SWAP); op(O_DROP); } +} +static void postfix(void) +{ + int n, was; + primary(); + for (;;) { + if (accept("[")) { + was = xs; rval(); expr(); rval(); + if (was && accept(":")) { + expr(); rval(); expect("]"); fnref("slice_s"); op(O_CALL); + lv = 0; xs = 1; xf = 0; lvi = -1; + } else if (was) { + expect("]"); fnref("at_s"); op(O_CALL); + lv = 0; xs = 0; xf = 0; lvi = -1; + } else { expect("]"); op(O_ADD); lv = 1; xs = 0; xf = 0; lvi = -1; } + } + else if (is("(")) { rval(); n = args(); lit(n); op(O_PICK); op(O_CALL); op(O_SWAP); op(O_DROP); xf = 0; xs = 0; lvi = -1; } + else if (accept("++")) incdec(1, 1); + else if (accept("--")) incdec(1, -1); + else if (accept("as")) ident(); + else return; + } +} +static void unary(void) +{ + if (accept("-")) { + unary(); rval(); xs = 0; + if (xf) { emit(1200); emit(0); op(O_SWAP); op(602); } + else { lit(0); op(O_SWAP); op(O_SUB); } + } + else if (accept("!")) { unary(); rval(); ftest(); seq_isz(); xf = 0; xs = 0; } + else if (accept("~")) { unary(); rval(); lit(-1); op(O_XOR); xf = 0; xs = 0; } + else if (accept("*")) { unary(); rval(); lv = 1; xs = 0; } + else if (accept("&")) { unary(); if (!lv) die("& needs an lvalue"); lv = 0; xs = 0; } + else if (accept("++")) { unary(); incdec(0, 1); } + else if (accept("--")) { unary(); incdec(0, -1); } + else if (tk == T_ID && (!strcmp(ts, "u") || !strcmp(ts, "m") || !strcmp(ts, "d"))) { next(); expect("*"); unary(); } /* u*x: a recast, no code */ + else postfix(); +} +static void bin(int minp) +{ + int p, a, b, lf, rf, ls, rs; char o[8]; + unary(); + while ((p = binprec()) && p >= minp) { + lf = xf; ls = xs; strcpy(o, ts); next(); rval(); + if (!strcmp(o, "&&")) { + ftest(); a = here; lit(0); op(O_JZ); bin(p + 1); rval(); ftest(); seq_nz(); + b = here; lit(0); op(O_JMP); setlit(a, here); lit(0); setlit(b, here); xf = 0; xs = 0; + } else if (!strcmp(o, "||")) { + ftest(); a = here; lit(0); op(O_JZ); lit(1); b = here; lit(0); op(O_JMP); + setlit(a, here); bin(p + 1); rval(); ftest(); seq_nz(); setlit(b, here); xf = 0; xs = 0; + } else { + bin(p + 1); rval(); rf = xf; rs = xs; + if ((ls || rs) && sis(o)) { + if (!(ls && rs)) die("a byte string needs a byte string on the other side"); + sbinop(o); + } else if ((lf || rf) && fis(o)) { fbinop(o, lf, rf); xs = 0; } + else { binop(o); xf = 0; xs = 0; } + } + lv = 0; lvi = -1; + } +} +static void expr(void) +{ + char o[8]; int dst, lf; + bin(1); dst = lvi; + if (is("=")) { + if (!lv) die("assignment needs an lvalue"); + next(); expr(); rval(); + op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); op(O_ST); lv = 0; + if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = xs; } + } else if (tk == T_OP && strlen(ts) >= 2 && ts[strlen(ts) - 1] == '=' && strcmp(ts, "==") && strcmp(ts, "!=") && strcmp(ts, "<=") && strcmp(ts, ">=")) { + if (!lv) die("assignment needs an lvalue"); + lf = xf; strcpy(o, ts); o[strlen(o) - 1] = 0; next(); + lit(0); op(O_PICK); op(O_LD); lv = 0; expr(); rval(); + if ((lf || xf) && fis(o)) fbinop(o, lf, xf); + else { binop(o); xf = 0; } + op(O_SWAP); lit(1); op(O_PICK); op(O_SWAP); op(O_ST); lv = 0; + if (dst >= 0) { lo[dst].flt = xf; lo[dst].str = 0; } + } +} + +/* ---- asm blocks: raw vm words ---- */ +static void asmblock(void) +{ + char ln[128][64], rn[256][64]; int la[128], nl = 0, ra[256], nr = 0, i, k; const char *c; + Loc *l; Sym *s; + expect("{"); + while (!accept("}")) { + if (tk == T_EOF) die("unterminated asm"); + if (tk == T_FLT) { emit(1200); emit(tv); next(); continue; } + if (tk == T_NUM) { lit(tv); next(); continue; } + if (tk == T_BSTR) { bstrlit(); continue; } + if (tk == T_STR) { strlit(); continue; } + if (accept("#")) { if (tk != T_NUM) die("# needs a number"); emit(tv); next(); continue; } + if (is("-")) { next(); if (tk == T_NUM && glue) { lit(-tv); next(); } else op(O_SUB); continue; } + if (accept(":")) { strcpy(ln[nl], ident()); la[nl++] = here; continue; } + if (strlen(ts) == 1 && (c = strchr(mn, ts[0]))) { op((int)(c - mn)); next(); continue; } + if (tk != T_ID) die("bad asm token"); + if ((l = findl(ts))) { laddr(l->slot); next(); continue; } + if ((s = find(ts)) && s->kind != K_FN) { lit(s->val); next(); continue; } + if (s) { fnref(ts); next(); continue; } + strcpy(rn[nr], ts); ra[nr++] = here; lit(0); next(); /* a label of this block */ + } + for (i = 0; i < nr; i++) { + for (k = -1; ++k < nl && strcmp(ln[k], rn[i]);) ; + if (k == nl) die("unknown asm label"); + setlit(ra[i], la[k]); + } +} + +/* ---- statements ---- */ +static int brk[256][64], nbrk[256], cont[256], nloop; +static int epi[1024], nepi; +static void stmt(void); +static void block(void) +{ + int save = nlo; + expect("{"); + while (!accept("}")) { if (tk == T_EOF) die("missing }"); stmt(); } + nlo = save; +} +static void jmp_to(int t) { lit(t); op(O_JMP); } +static void loopbody(int contaddr) +{ + nbrk[nloop] = 0; cont[nloop++] = contaddr; + stmt(); + nloop--; +} +static void patchbreaks(void) { int i; for (i = 0; i < nbrk[nloop]; i++) setlit(brk[nloop][i], here); } +static void stmt(void) +{ + int a, b, c, d; + if (accept(";")) return; + if (is("{")) { block(); return; } + { + int nw = optype(), n; + if (nw >= 0 || accept("let") || accept("var")) { + if (nw < 0) nw = 1; + do { + Loc *l = &lo[nlo++]; strcpy(l->name, ident()); l->arr = 0; + if (nw > 1) { l->slot = nslot; nslot += nw; l->arr = 1; } + else { + if (accept("[")) { n = cexpr(); expect("]"); nslot += n; l->slot = nslot - n; l->arr = 1; } + else l->slot = nslot++; + } + l->flt = 0; l->str = 0; + if (accept("=")) { expr(); rval(); laddr(l->slot); op(O_ST); l->flt = xf; l->str = xs; } + } while (accept(",")); + expect(";"); return; + } + } + if (accept("unchecked")) { stmt(); return; } + if (accept("if")) { + expect("("); expr(); rval(); ftest(); expect(")"); + a = here; lit(0); op(O_JZ); stmt(); + if (accept("else")) { b = here; lit(0); op(O_JMP); setlit(a, here); stmt(); setlit(b, here); } + else setlit(a, here); + return; + } + if (accept("while")) { + c = here; expect("("); expr(); rval(); ftest(); expect(")"); + a = here; lit(0); op(O_JZ); + loopbody(c); jmp_to(c); setlit(a, here); patchbreaks(); return; + } + if (accept("for")) { + int save = nlo; + expect("("); + if (!is(";")) { if (is("let") || is("var") || tk == T_ID || is("[")) { stmt(); goto cond; } expr(); rval(); op(O_DROP); } + expect(";"); + cond: c = here; + if (!is(";")) { expr(); rval(); ftest(); } else { lit(1); xf = 0; } + expect(";"); + a = here; lit(0); op(O_JZ); b = here; lit(0); op(O_JMP); + d = here; + if (!is(")")) { expr(); rval(); op(O_DROP); } + expect(")"); jmp_to(c); + setlit(b, here); + loopbody(d); jmp_to(d); setlit(a, here); patchbreaks(); + nlo = save; return; + } + if (accept("break")) { if (!nloop) die("break outside a loop"); brk[nloop - 1][nbrk[nloop - 1]++] = here; lit(0); op(O_JMP); expect(";"); return; } + if (accept("continue")) { if (!nloop) die("continue outside a loop"); jmp_to(cont[nloop - 1]); expect(";"); return; } + if (accept("return")) { + if (!is(";")) { expr(); rval(); } else lit(0); + expect(";"); epi[nepi++] = here; lit(0); op(O_JMP); return; + } + if (is("asm")) { next(); asmblock(); return; } + expr(); rval(); op(O_DROP); expect(";"); +} + +/* ---- top level ---- */ +static void fndef(int ret) +{ + char *n = ident(); Sym *s; int np = 0, tags = 0, t, frame, i, helper, busy, done, check, node; + if (dsn && !dnm[0]) strcpy(dnm, n); + char pn[16][64]; + s = find(n); + if (!s || s->kind != K_FN) { s = add(n, K_FN, 0); s->defd = 0; } + expect("("); + if (!accept(")")) { + do { + t = 0; ptag = 0; + if (optype() >= 0) t = ptag == 1 ? 1 : ptag == 2 ? 2 : 0; /* the type is read and dropped */ + else if (accept(".")) { /* .m name, .d name */ + if (is("m")) t = 1; else if (is("d")) t = 2; else die("a parameter tag is .m or .d"); + next(); + } + if (np < 12) tags += t << (2 * np); + strcpy(pn[np++], ident()); + } while (accept(",")); + expect(")"); + } + s->sig = ret + 2 * np + 64 * tags; + if (accept(";")) return; /* prototype */ + if (s->defd) die("function defined twice"); + s->val = here; s->defd = 1; +#ifdef ZC0DOCS + { /* the header is the source from the item's first token to the { that opens the body */ + int hl = tstart - hdr_start; + sgname[0] = 0; + if (hl > 0 && hl < (int)sizeof sgtext - 1) { + memcpy(sgtext, src + hdr_start, hl); sgtext[hl] = 0; + while (hl && isspace((unsigned char)sgtext[hl - 1])) sgtext[--hl] = 0; + strcpy(sgname, n); + } + } +#endif + nlo = 0; nslot = 0; nepi = 0; infn = 1; + for (i = 0; i < np; i++) { strcpy(lo[nlo].name, pn[i]); lo[nlo].arr = 0; lo[nlo].flt = 0; lo[nlo].str = 0; lo[nlo++].slot = nslot++; } + /* retain the caller's pointer until the frame has its final segment. */ + helper = !find("frame_busy") || !strcmp(s->name, "frame_grow") || !strcmp(s->name, "frame_leave"); + lit(ZFP); op(O_LD); if (!helper) { lit(0); op(O_PICK); } + frame = here; lit(0); op(O_SUB); lit(ZFP); op(O_ST); + if (!helper) { + lit(find("frame_busy")->val); op(O_LD); busy = here; lit(0); op(O_JZ); + done = here; lit(0); op(O_JMP); setlit(busy, here); + lit(ZFP); op(O_LD); lit(find("frame_floor")->val); op(O_LD); + lit(find("frame_guard")->val); op(O_LD); op(O_ADD); op(O_LT); check = here; lit(0); op(O_JZ); + lit(ZFP); op(O_LD); lit(1); op(O_PICK); lit(ZFP); op(O_ST); + fnref("frame_grow"); op(O_CALL); lit(ZFP); op(O_ST); + busy = here; lit(0); op(O_JMP); + setlit(check, here); setlit(done, here); op(O_DROP); setlit(busy, here); + } + for (i = np; i--;) { laddr(i); op(O_ST); } + block(); + lit(0); + for (i = 0; i < nepi; i++) setlit(epi[i], here); + lit(ZFP); op(O_LD); lit(nslot); op(O_ADD); lit(ZFP); op(O_ST); + if (!helper) { + lit(find("frame_node")->val); op(O_LD); node = here; lit(0); op(O_JZ); + lit(ZFP); op(O_LD); lit(find("frame_top")->val); op(O_LD); op(O_SUB); check = here; lit(0); op(O_JZ); + done = here; lit(0); op(O_JMP); setlit(check, here); + fnref("frame_leave"); op(O_CALL); lit(ZFP); op(O_ST); setlit(done, here); setlit(node, here); + } + op(O_RET); + setlit(frame, nslot); + infn = 0; +} +/* enhance name(params) [-> n] micro "recipe"; or ... vm { asm }; + emits the install descriptor; the booted genome installs it */ +static int nenh; +static void enhdef(void) +{ + char *n, pn[8][64]; int np = 0, nout = 1, d, na, pay, len = 0, i, j; Sym *s; + if (tk == T_NUM) { nout = tv; next(); } /* enhance 2 name(...) */ + n = ident(); + expect("("); + if (!accept(")")) { do { if (np == 8) die("at most 8 inputs"); strcpy(pn[np++], ident()); } while (accept(",")); expect(")"); } + d = here; for (i = 0; i < 7; i++) emit(0); + na = here; for (i = 0; i < np; i++) emit(0); + for (i = 0; i < np; i++) { img[na + i - GORG] = here; for (j = 0; pn[i][j]; j++) emit((unsigned char)pn[i][j]); emit(0); } + if (accept("micro")) { pay = here; while (tk == T_STR) { for (j = 0; ts[j]; j++) emit((unsigned char)ts[j]); next(); } emit(0); } + else if (accept("vm")) { pay = here; asmblock(); op(O_RET); len = here - pay; img[d + 1 - GORG] = 1; } + else die("expected micro or vm"); + img[d - GORG] = nenh; img[d + 2 - GORG] = np; img[d + 3 - GORG] = nout; + img[d + 4 - GORG] = pay; img[d + 5 - GORG] = len; img[d + 6 - GORG] = na; + s = add(n, K_ENH, nenh++); s->sig = np + nout * 16 + d * 256; + expect(";"); +} +static void top1(void) +{ + { + if (accept("unchecked")) { top1(); return; } + if (accept("fn")) { fndef(0); return; } + if (accept(".")) { if (strcmp(ident(), "u")) die("the only return tag is .u"); fndef(1); return; } /* .u name(...) */ + if (accept("const")) { optype(); do { char *n = ident(); if (dsn && !dnm[0]) strcpy(dnm, n); expect("="); add(n, K_CONST, cexpr()); } while (accept(",")); expect(";"); return; } + if (accept("enhance")) { enhdef(); return; } + { + int nw, typed, k, a, rt; + ptag = 0; nw = optype(); typed = nw >= 0; rt = ptag == 3; + if (typed || accept("let") || accept("var")) { + if (isfn()) { fndef(rt); return; } + do { + char *n = ident(); + if (dsn && !dnm[0]) strcpy(dnm, n); + k = 0; + if (typed) k = nw > 1 ? nw : 0; + else if (accept("[")) { k = (int)cexpr(); expect("]"); } + if (k > 1) { + a = here; while (k--) emit(0); add(n, K_GARR, a); + } else { + a = here; add(n, K_GVAR, a); emit(0); + if (accept("=")) { + if (tk == T_STR) { int i, l = (int)strlen(ts); img[a - GORG] = here; for (i = 0; i < l; i++) emit((unsigned char)ts[i]); emit(0); next(); } + else img[a - GORG] = cexpr(); + } + } + } while (accept(",")); + expect(";"); return; + } + } + die("expected fn, var or const"); + } +} + +/* one item, with the doc comment above it kept under the name it declares */ +static void top(void) +{ + while (tk != T_EOF) { + dsn = 0; dnm[0] = 0; +#ifdef ZC0DOCS + sgname[0] = 0; hdr_start = tstart; +#endif + if (dn && dline == line) { memcpy(dsnap, dbuf, dn); dsn = dn; dline = 0; } + top1(); +#ifdef ZC0DOCS /* enabled for images that carry editor documentation */ + if (dsn && dnm[0]) { + int j = here, a, i = 0, k, c; uint64_t w; char nm[80]; + lit(0); op(O_JMP); a = here; + emit(dsn); + while (i < dsn) { + w = 0; k = 0; + while (k < 8 && i < dsn) { c = (unsigned char)dsnap[i]; if (c > 255) c = 63; w |= (uint64_t)c << (k * 8); k++; i++; } + emit((int64_t)w); + } + if (!(dsn & 7)) emit(0); + setlit(j, here); + sprintf(nm, "doc %s", dnm); + add(nm, K_DOC, a); + } + if (sgname[0]) { /* the signature, as the header's own text, under "sig name" */ + int j = here, a, i = 0, k, c, sl = (int)strlen(sgtext); uint64_t w; char nm[96]; + lit(0); op(O_JMP); a = here; + emit(sl); + while (i < sl) { + w = 0; k = 0; + while (k < 8 && i < sl) { c = (unsigned char)sgtext[i]; w |= (uint64_t)c << (k * 8); k++; i++; } + emit((int64_t)w); + } + if (!(sl & 7)) emit(0); + setlit(j, here); + sprintf(nm, "sig %s", sgname); + add(nm, K_DOC, a); + } +#endif + dsn = 0; dnm[0] = 0; + } +} + +int main(void) +{ +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + char *buf = malloc(65536); int capacity = 65536, n = 0, i, j; Sym *st; + if (!buf) { fprintf(stderr, "zc0: cannot allocate source buffer\n"); return 1; } + for (;;) { + size_t got; + if (n + 1 == capacity) { + char *next; + if (capacity > 0x7fffffff / 2) { fprintf(stderr, "zc0: source is too large\n"); return 1; } + capacity *= 2; next = realloc(buf, (size_t)capacity); + if (!next) { fprintf(stderr, "zc0: cannot grow source buffer\n"); return 1; } + buf = next; + } + got = fread(buf + n, 1, (size_t)(capacity - n - 1), stdin); n += (int)got; + if (!got) { if (ferror(stdin)) { fprintf(stderr, "zc0: source read failed\n"); return 1; } break; } + } + buf[n] = 0; src = buf; next(); + /* image header: set the frame pointer, jump to start, then the symbol blob pointer and image end */ + lit(FTOP); lit(ZFP); op(O_ST); fnref("start"); op(O_JMP); emit(0); emit(0); + top(); + for (i = 0; i < nfx; i++) { + Sym *s = &sy[fx[i].sym]; + if (!s->defd) { fprintf(stderr, "zc0: undefined function %s\n", s->name); return 1; } + setlit(fx[i].at, s->val); + } + if (!(st = find("start")) || !st->defd) { fprintf(stderr, "zc0: no start\n"); return 1; } + /* symbol blob: kind, value, sig, name chars, 0 ... then a 0 kind */ + img[5] = here; + for (i = 0; i < nsy; i++) { + emit(sy[i].kind); emit(sy[i].val); emit(sy[i].sig); + for (j = 0; sy[i].name[j]; j++) emit((unsigned char)sy[i].name[j]); + emit(0); + } + emit(0); + img[6] = here; + printf("#define GORG %d\n#define GLEN %d\nstatic const int64_t genome[] = {", GORG, here - GORG); + for (i = 0; i < here - GORG; i++) printf("%s%lld", !i ? "\n" : i % 12 ? "," : ",\n", (long long)img[i]); + printf("\n};\n"); + return 0; +} diff --git a/src/tools/zcc b/src/tools/zcc new file mode 100755 index 0000000..19904ea --- /dev/null +++ b/src/tools/zcc @@ -0,0 +1,54 @@ +#!/bin/bash +# zcc: compiles a ZC (ZygoteC) program into a static binary. the binary holds +# the runtime, the program and a memory map sized to them, and needs nothing +# from the system but the kernel (linux x86-64 and any host with a static +# libc). the program reads and writes through the same syscalls as zy(1). +# +# src/tools/zcc [-o OUT] [-k] FILE.zc +# +# -o OUT the binary to write (default: FILE without .zc) +# -k keep the build directory and print where it is +# CC the C compiler (default cc). CFLAGS adds to the default -O2 +# +# the build is three steps: automap sizes the map to the program and writes the +# genome (the compiled program image) for it; the micro core and its linux port +# are compiled against that map; and the link is static. make run PROG= builds +# the same image without the static link. +set -e +here="$(cd "$(dirname "$0")/../.." && pwd)" +CC=${CC:-cc} +out=""; keep=0 +while [ $# -gt 0 ]; do + case "$1" in + -o) [ $# -ge 2 ] || { echo "zcc: -o needs a name" >&2; exit 2; }; out="$2"; shift 2 ;; + -k) keep=1; shift ;; + -h|--help) sed -n '2,15p' "$0"; exit 0 ;; + -*) echo "zcc: unknown option $1" >&2; exit 2 ;; + *) break ;; + esac +done +[ $# -eq 1 ] || { echo "usage: src/tools/zcc [-o OUT] [-k] FILE.zc" >&2; exit 2; } +src="$1" +[ -f "$src" ] || { echo "zcc: no such file: $src" >&2; exit 1; } +case "$src" in *.zc) ;; *) echo "zcc: the source must end in .zc" >&2; exit 2 ;; esac +[ -n "$out" ] || { out="${src%.zc}"; out="$(basename "$out")"; } +case "$src" in /*) abs="$src" ;; *) abs="$PWD/$src" ;; esac + +work="$(mktemp -d "${TMPDIR:-/tmp}/zcc.XXXXXX")" +cleanup() { [ "$keep" = 1 ] && echo "zcc: build kept in $work" >&2 || rm -rf "$work"; } +trap cleanup EXIT + +# the map and the genome for this program only +( cd "$here" && bash src/tools/automap.sh run "$work/run" src/runtime/run.zc "$abs" ) >/dev/null +cp "$here/src/micro/micro.c" "$here/src/micro/micro_port.h" "$here/src/micro/port_linux.c" \ + "$here/src/core/fops.h" "$here/src/core/bops.h" "$here/src/core/stack.h" "$work/run/" + +# static where the toolchain allows it; a plain link otherwise, with a note +static=-static +if ! printf 'int main(void){return 0;}\n' | $CC -x c -static -o "$work/probe" - >/dev/null 2>&1; then + static="" + echo "zcc: this toolchain cannot link -static; the binary is dynamic" >&2 +fi +$CC -O2 -w -fwrapv ${CFLAGS:-} $static "$work/run/micro.c" "$work/run/port_linux.c" \ + -I"$work/run" -o "$out" +echo "zcc: wrote $out ($(wc -c < "$out" | tr -d ' ') bytes)" |
