diff options
| author | 2026-10-14 07:28:39 +0000 | |
|---|---|---|
| committer | 2026-10-14 07:28:39 +0000 | |
| commit | 7451eb090d890fb5ea5e4d40627a1b4df15a49de (patch) | |
| tree | 357bdf4b528e860bffa688865306f656b788d2c2 /src/tools/zc0.c | |
| parent | analyzer: close the ownership gaps that stopped the compiler compiling itself (diff) | |
build: embed images with #embed, keep everything generated in build/
the cores take their genome in with c23 #embed through src/core/genome.h,
so images are plain little-endian word files (genome.bin) instead of
generated c headers. zc0 writes that format, mkgenome checks an image
against its map and shows a compiler's diagnostics when it gets them,
and the javascript vm and the editor's vm host read the same files.
src/tools/findcc.sh picks a compiler with #embed (clang 19 or gcc 15).
zc gets an embed declaration, embed "file" as [type] name, for globals
and locals: a byte string by default, char*, numbers read little-endian,
and arrays of numbers. the compiler and language server carry their
library and source texts through it, so embed.sh and the text it
spliced into compiler.zc are gone.
make writes executables to build/bin, the default map to build/core/map,
the web page to build/web and the extension staging to build/vsix, and
the genome now rebuilds when its sources change. the test suite lays
out its own map and bootstrap and reads nothing from build/.
also: rt.put_s of a byte string in zc0 images, retained function
sources no longer count as live objects, a missing include no longer
cascades into undefined-name errors, consecutive comment lines join
into one hover paragraph, and stale test fixtures follow the include
format.
Diffstat (limited to 'src/tools/zc0.c')
| -rw-r--r-- | src/tools/zc0.c | 97 |
1 files changed, 87 insertions, 10 deletions
diff --git a/src/tools/zc0.c b/src/tools/zc0.c index 446557b..5f5b97d 100644 --- a/src/tools/zc0.c +++ b/src/tools/zc0.c @@ -1,7 +1,8 @@ /* 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 + image (vm words placed at GORG, as little-endian 64-bit words) on stdout, in + the same image layout the resident compiler's image mode produces. the cores + embed that file with #embed (src/core/genome.h). 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. */ @@ -454,12 +455,13 @@ static void strlit(void) /* inline word string: jump over it, push its address * static void bstrlit(void) { int j = here, a, i = 0, k, n = (int)strlen(ts); + const char *b = 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++; } + while (k < 8 && i < n) { w |= (uint64_t)(unsigned char)b[i] << (k * 8); k++; i++; } emit((int64_t)w); } if ((n & 7) == 0) emit(0); @@ -497,7 +499,7 @@ static void primary(void) else if (((s->sig >> 4) & 15) != 1) die("multiple enhancement outputs need asm"); xs = 0; xf = 0; return; } - if (!strcmp(n, "put_s") && na == 1 && xs) fnref("put_bs"); + if ((!strcmp(n, "put_s") || !strcmp(n, "rt.put_s")) && na == 1 && xs) fnref(n[0] == 'r' ? "rt.put_bs" : "put_bs"); else if (!strcmp(n, "len")) fnref("len_s"); else fnref(n); op(O_CALL); @@ -794,6 +796,77 @@ static void enhdef(void) s = add(n, K_ENH, nenh++); s->sig = np + nout * 16 + d * 256; expect(";"); } +/* embed "path" as [type] name; at the top level: a global holding the file's bytes, + read when the image is built. the path is relative to the working directory. + bstr (the default) packs the bytes; char* keeps one character to a word; a + number takes them little-endian; T[] and T[n] lay them out as the array's + elements. the same rules as the resident compiler's kw_embed. */ +static int embed_width(const char *t, int star) +{ + if (star) return 0; + if (!strcmp(t, "char") || !strcmp(t, "u8") || !strcmp(t, "i8") || !strcmp(t, "byte")) return 1; + if (!strcmp(t, "u16") || !strcmp(t, "i16")) return 2; + if (!strcmp(t, "u32") || !strcmp(t, "i32") || !strcmp(t, "f32")) return 4; + if (!strcmp(t, "int") || !strcmp(t, "word") || !strcmp(t, "bool") || !strcmp(t, "float") || !strcmp(t, "f64")) return 8; + return 0; +} +static void embeddecl(void) +{ + char path[1024], ty[64] = "", *n; int star = 0, arr = -2, w, i, a; long len; unsigned char *buf; FILE *f; + if (tk != T_STR) die("embed takes a path in quotes: embed \"file\" as name;"); + snprintf(path, sizeof path, "%s", ts); next(); + expect("as"); + if (tk == T_ID) { + Lex L; lexsave(&L); next(); + int named = is(";"); lexrest(&L); + if (!named) { + snprintf(ty, sizeof ty, "%s", ts); next(); + while (accept("*")) star++; + if (accept("[")) { arr = -1; if (tk == T_NUM) { arr = (int)tv; next(); } expect("]"); } + } + } + n = ident(); expect(";"); + if (!(f = fopen(path, "rb"))) die("can't read the file named by embed"); + fseek(f, 0, SEEK_END); len = ftell(f); fseek(f, 0, SEEK_SET); + buf = malloc((size_t)len + 1); + if (!buf || fread(buf, 1, (size_t)len, f) != (size_t)len) die("can't read the file named by embed"); + fclose(f); + if (arr == -2 && (!ty[0] || (!strcmp(ty, "bstr") && !star))) { + a = here; add(n, K_GVAR, a); emit(0); img[a - GORG] = here; emit(len); + for (i = 0; i < len; i += 8) { uint64_t u = 0; for (w = 0; w < 8 && i + w < len; w++) u |= (uint64_t)buf[i + w] << (w * 8); emit((int64_t)u); } + if (!(len & 7)) emit(0); + } else if (arr == -2 && !strcmp(ty, "char") && star == 1) { + a = here; add(n, K_GVAR, a); emit(0); img[a - GORG] = here; + for (i = 0; i < len; i++) emit(buf[i]); + emit(0); + } else if (arr == -2) { + uint64_t u = 0; w = embed_width(ty, star); + if (!w) die("embed declares a bstr, a char*, a number or an array of numbers"); + if (len > w) die("the embedded file is larger than the type it is declared as"); + if (!strcmp(ty, "f32") && len != 4) die("an embedded f32 takes exactly 4 bytes"); + if ((!strcmp(ty, "float") || !strcmp(ty, "f64")) && len != 8) die("an embedded float takes exactly 8 bytes"); + for (i = 0; i < len; i++) u |= (uint64_t)buf[i] << (i * 8); + if (ty[0] == 'i' && w < 8 && (u >> (w * 8 - 1) & 1)) u |= ~(uint64_t)0 << (w * 8); + if (!strcmp(ty, "f32")) u = fop_s2f(u); + a = here; add(n, K_GVAR, a); emit((int64_t)u); + } else { + int count; w = embed_width(ty, star); + if (!w) die("embed declares a bstr, a char*, a number or an array of numbers"); + if (len % w) die("the embedded file's size is not a whole number of elements"); + count = (int)(len / w); + if (arr >= 0 && count > arr) die("the embedded file is larger than the array it is declared as"); + if (arr >= 0) count = arr; + a = here; + if (!strcmp(ty, "char")) { for (i = 0; i < count; i++) emit(i < len ? buf[i] : 0); } + else { + long bytes = (long)count * w; + for (i = 0; i < bytes; i += 8) { uint64_t u = 0; for (int k = 0; k < 8 && i + k < bytes; k++) u |= (uint64_t)(i + k < len ? buf[i + k] : 0) << (k * 8); emit((int64_t)u); } + if (!bytes) emit(0); + } + add(n, K_GARR, a); + } + free(buf); +} static void top1(void) { if (accept("namespace")) { @@ -802,6 +875,7 @@ static void top1(void) expect(";"); return; } if (accept("include")) { do { ident(); } while (accept(",")); expect(";"); return; } + if (accept("embed")) { embeddecl(); return; } { if (accept("unchecked")) { top1(); return; } if (accept("fn")) { fndef(0); return; } @@ -898,8 +972,9 @@ static void top(void) int main(int argc, char **argv) { if (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) { - fputs("usage: zc0 [--help|-h] < source.zc > genome.h\n" - "compiles bootstrap source from stdin into a c genome header on stdout.\n" + fputs("usage: zc0 [--help|-h] < source.zc > genome.bin\n" + "compiles bootstrap source from stdin into a genome image on stdout:\n" + "little-endian 64-bit words, the format the cores #embed.\n" "source must define start(); memory layout is selected when zc0 is built.\n" "options:\n --help, -h show this help and exit\n", stdout); return 0; @@ -942,8 +1017,10 @@ int main(int argc, char **argv) } 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; + for (i = 0; i < here - GORG; i++) { + unsigned char b[8]; uint64_t u = (uint64_t)img[i]; + for (j = 0; j < 8; j++) b[j] = (unsigned char)(u >> (j * 8)); + if (fwrite(b, 1, 8, stdout) != 8) { fprintf(stderr, "zc0: write failed\n"); return 1; } + } + return fflush(stdout) ? 1 : 0; } |
