aboutsummaryrefslogtreecommitdiff
path: root/docs/history/07-zygote/code
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 09:09:21 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 09:09:21 +0330
commitdc7f7407e08f54e4a2a3c1d1723b2a7d6959532d (patch)
treed1de9f471a99951c57896f85b5e5523eeb96cee1 /docs/history/07-zygote/code
parenttodo: sync docs, examples and fixtures with the include format, and test acto... (diff)
docs: archive the frozen stage code as one compressed tarball
docs/history kept six stages with a full copy of the project under code/, 3.8 mb of sources duplicated from stage to stage and already superseded. store them as docs/history/code-snapshots.tar.gz, 0.66 mb, and extract one in place to read it. every stage's notes.md stays browsable, and the index says where the code went and how to get it back. the top-level README points at the archive too. its other two links into docs/history, 21-micro and 35-bench, were notes that were never kept and do not resolve; the index now records that.
Diffstat (limited to 'docs/history/07-zygote/code')
-rw-r--r--docs/history/07-zygote/code/README.md168
-rw-r--r--docs/history/07-zygote/code/aslib.s67
-rw-r--r--docs/history/07-zygote/code/core.c276
-rw-r--r--docs/history/07-zygote/code/examples/asmain.s9
-rw-r--r--docs/history/07-zygote/code/examples/echo.s1
-rw-r--r--docs/history/07-zygote/code/examples/fact.s3
-rw-r--r--docs/history/07-zygote/code/examples/hello.s2
-rw-r--r--docs/history/07-zygote/code/examples/sm.s3
-rw-r--r--docs/history/07-zygote/code/gen.s9
-rw-r--r--docs/history/07-zygote/code/genome.h39
-rw-r--r--docs/history/07-zygote/code/io_linux.c6
-rw-r--r--docs/history/07-zygote/code/mkgenome.c35
-rw-r--r--docs/history/07-zygote/code/repl.s11
-rwxr-xr-xdocs/history/07-zygote/code/test.sh18
14 files changed, 0 insertions, 647 deletions
diff --git a/docs/history/07-zygote/code/README.md b/docs/history/07-zygote/code/README.md
deleted file mode 100644
index 924c48b..0000000
--- a/docs/history/07-zygote/code/README.md
+++ /dev/null
@@ -1,168 +0,0 @@
-# tinyvm zygote: notes and transcript
-
-a tiny stack cpu that started as a c89 code golf entry and ended up as a self-hosting runtime. a small native core jit-compiles vm code to arm64, and the assembler lives in vm memory next to the program, where the program can modify it.
-
-this folder only has the latest code. earlier stages are described in the transcript below, but their code isn't included.
-
-## files
-
-`core.c` is the native core. it holds the arm64 jit, the stubs and the invalidation logic, and is the only native code in the system. `gen.s` is the genome header and boot loader, in vm assembly. `aslib.s` is the assembler, written in vm assembly. `mkgenome.c` is a build tool that assembles `gen.s` + `aslib.s` at address 48000 into `genome.h`, which is the generated result and is checked in. `repl.s` is a demo that mutates the resident assembler and repeatedly assembles and runs input chunks. `io_linux.c` is the `Q`/`G`/`J` test harness. `test.sh` builds for arm64 linux and runs everything under qemu. `examples/` has hello, a recursive factorial (prints in binary), echo, a self-patching test, and `asmain.s`, the standalone wrapper that turns `aslib.s` into a stdin-to-stdout assembler.
-
-## build
-
-```
-cc -o mkgenome mkgenome.c && cat gen.s aslib.s | ./mkgenome >genome.h # only after changing gen.s or aslib.s
-cc -O2 core.c io.c -o zy # io.c provides char Q(void), void G(char), and J
-./zy <examples/hello.s
-```
-
-`J` is a `char` by default. compile with `-DJTYPE=int` if yours is an int. `ZYTRACE=1` logs every translation and invalidation on stderr. the program source ends at eof or at a backtick. anything after the backtick stays unread, so the program can read it as its own input.
-
-## the instruction set
-
-every word is 32 bits and memory is 65536 words. an odd word is a literal that pushes `w>>1` (31 bits, sign extended). an even word is an opcode, `w/2`. there's a data stack and a return stack, both stored inside memory. the data stack grows down from 65000 and the return stack grows up from 60000.
-
-```
-0 q halt 5 ~ swap 10 , in (Q) 15 * mul 20 < less
-1 @ load 6 c call 11 ? empty (J) 16 & and
-2 ! store 7 j jump 12 . out (G) 17 | or
-3 $ pick 8 z jz 13 + add 18 ^ xor
-4 _ drop 9 r ret 14 - sub 19 > shr
-```
-
-stack effects: `!` is `(v addr --)`, `$` is `(n -- copy of the nth item)` so `0 $` is dup, `z` is `(cond addr --)` and jumps when cond is 0, and `c` and `j` take their target from the stack. there's no div or mod, because a divider can't finish in one cycle in hardware. division is done in software.
-
-assembler syntax uses whitespace-separated tokens. `:name` defines a label, a single op character is that opcode, `"text` emits raw characters, `#x` emits a raw number or label address, and anything else becomes a literal push of a number or label. when a label is defined twice, the last definition wins. a label nobody references emits nothing, so `:;like_this` works as a comment.
-
-## memory map and abi
-
-```
-0.. the program (assembled at origin 0 by the boot loader)
-20000..44999 boot source buffer, one char per word (free after boot)
-45000..47999 boot label table (free after boot)
-48000 genome: 48000 boot, 48002 a.run (call with 48002 c),
- 48004 &a.src 48005 &a.dst 48006 &a.org 48007 &a.lab 48008 &a.n 48009 &a.ops
-60000 up return stack
-65000 down data stack
-```
-
-to use the resident assembler, store a source address into `48004 @`, the destination into `48005 @`, the run-at origin into `48006 @` and a label scratch area into `48007 @`, then do `48002 c`. the word count ends up in `48008 @`. `48009 @` points to the mnemonic table `q@!$_~cjzr,?.+-*&|^><`, which you can rewrite. boot limits: the program has to fit in 20000 words and the source in 25000 chars.
-
-## how the core works
-
-registers: x19 holds the memory base, x20 the data stack pointer, x21 the return stack pointer, x22 the translation table and x23 the state block (memory, s, r, table, coverage map, Q, G, &J, store hook).
-
-compilation is lazy. the first time control reaches an address, that block is translated from live memory. a block ends at call, jump, ret or halt. a `z` doesn't end it: the not-taken side keeps compiling, which makes superblocks. a literal followed by `c`, `j` or `z` is fused into a constant target. every exit goes through a 5-instruction dispatch stub that bounds-checks the target, loads `tab[t]` and branches to it directly when it's already compiled. otherwise it returns to c, which compiles the block and re-enters.
-
-self-modification is handled with a byte map of which words some live translation covers. a store checks that byte inline: an uncovered target, the usual case, gets a plain store. a covered target calls the hook, which drops every translation containing that word and makes the jit code return to c at the next instruction, so even patches to the very next word take effect. writing back the same value is a no-op. if the 16 mb code buffer fills, everything is flushed.
-
-platform: arm64 only. the darwin path uses `MAP_JIT`, `pthread_jit_write_protect_np` and `sys_icache_invalidate`. linux uses rwx mmap and `__builtin___clear_cache`. only the linux arm64 path has actually been run, under qemu. the darwin path follows apple's documented recipe but hasn't been tested. under the hardened runtime it also needs the allow-jit entitlement.
-
-differences from the golfed reference vm, which leaves all of these undefined: shift counts wrap at 32, stores outside memory are ignored, and jumping outside memory halts.
-
-## test results (linux arm64 via qemu)
-
-everything in `test.sh` passes: hello, factorial, echo after the backtick, self-patching that matches the golfed vm byte for byte, the self-hosted assembler running on the zygote and producing the same bytes as the c assembler, and the repl that prints `hi` and then `yo` from two chunks assembled into the same address. the trace shows the first chunk's translation being dropped. earlier in the session, `asm.s` running on the zygote reproduced the c assembler's output for its own source too. a 20 million iteration loop took 0.54 s on the zygote against 2.18 s for the golfed vm interpreter under the same qemu, and that 0.54 includes assembling the source at startup.
-
-## open ends
-
-- the llvm ir port was asked for and then dropped in favor of the zygote before any of it was written.
-- the assembler library uses globals, so it isn't re-entrant. code that it assembles at runtime shouldn't call it while an outer `a.run` is still running.
-- translations are only reclaimed by a full flush, and the hook rescans the block list on every store to a covered word. that's fine unless code rewrites itself in a hot loop.
-- none of the later stages are golfed. the golf-sized code is the reference vm and assembler from stage 2.
-
----
-
-## transcript
-
-each of my messages is quoted exactly, followed by a summary of the reply.
-
-### 1
-
-> codegolf a tiny cpu vm in C89. the goal is to achieve the lowest character count possible. include an assembler as well (scored separately) and a hello world program (not scored, but elegance and simplicity is a bonus)
-> it must be capable of re-entrant code and a realistically workable instruction set.
-
-**summary:** a two-stack forth-style machine. the vm was 384 bytes: one ternary chain inside `while(o=m[p++])`, the top two stack items cached in `a` and `b`, the program loaded with `read(open(...))` so it needs no headers, and both stacks inside the 65536-word memory. it had 22 opcodes including div and mod, a two-word literal, and jump/call targets popped from the stack. the assembler was 348 bytes and two-pass, using `strcspn` instead of `strchr` so the implicit int return stays safe on 64-bit. hello world was a pointer loop over a string with a zero terminator. re-entrancy was shown with a recursive factorial plus a recursive decimal printer.
-
-### 2
-
-> your cpu should also be realistically close to hardware (translating it to logical circuitry hardware should be fairly easy) and sport an acceptably fast execution (if hypothetically ported to hardware, not in software)
->
-> assume i/o is handled by two external functions, `char Q()` (getchar) and `void G(char)` (putchar) and a state variable J (boolean, shows if input is empty).
->
-> don't be afraid to use macros.
-
-**summary:** no reply was sent to this message at the time. it was answered together with message 3.
-
-### 3
-
-> pro tip: if you define a macro as `!o--`, you can save 2 characters per if condition
-> oh and btw, newlines are free
-
-**summary:** used the tip as `#define X !--o?`, with pre-decrement because opcode 0 (halt) is already handled by the loop, and with the `?` folded into the macro and free newlines used as separators. the hardware-oriented redesign: bit 0 of each word is a literal flag, so literals take one cycle like the j1 cpu. div and mod were dropped because they can't finish in one cycle and were replaced by an arithmetic shift right, giving 8 alu ops that decode from 3 bits. every instruction does a fixed amount of work with at most one data memory access, and call and jump share a datapath (`r-=o`). i/o now goes through `Q`, `G` and a `?` op that reads `J`. the result was the vm at 339 bytes and the assembler at 346. the final golfed code:
-
-```c
-#define X !--o?
-int m[1<<16],p,s=65e3,r=6e4,o,a,b;main(c,v)char**v;{read(open(v[1],0),m,s);while(o=m[p++])a=m[s],b=m[s+1],o&1?m[--s]=o>>1:(o/=2,X
-m[s]=m[a]:X
-m[a]=b,s+=2:X
-m[s]=m[s-~a]:X
-s++:X
-m[s]=b,m[s+1]=a:(o-=2)<1?m[r]=p,r-=o,p=a,s++:X
-p=b?p:a,s+=2:X
-p=m[--r]:X
-m[--s]=Q():X
-m[--s]=J:X
-G(a),s++:(m[++s]=X
-b+a:X
-b-a:X
-b*a:X
-b&a:X
-b|a:X
-b^a:X
-b>>a:b<a));}
-```
-
-```c
-char t[9999][99],*o="q@!$_~cjzr,?.+-*&|^><",*q;A[9999],k,i,j,n,P;E(y){n++;write(P,&y,4);}V(char*s){for(j=k;j--&&(*t[j]-58||strcmp(t[j]+1,s)););return~j?A[j]:atoi(s);}main(){while(~scanf("%s",t[k]))k++;for(;P<2;P++)for(n=i=0;i<k;i++)if(*(q=t[i])==34)while(*++q)E(*q);else*q==58?A[i]=n:!q[1]&&o[j=strcspn(o,q)]?E(j*2):*q==35?E(V(q+1)):E(V(q)*2+1);}
-```
-
-### 4
-
-> now let's move in stages. write a compiler that takes this assembler's code and converts it into darwin arm
-
-**summary:** an ungolfed c89 compiler from vm assembly to darwin arm64 assembly. every word got its own label, the image was emitted as an addressable data array, x19/x20/x21 held the memory base and the two stack pointers, and computed jumps went through a table of offsets. a literal followed by a jump became a direct branch. `Q` and `G` went through `sxtb`, since char is signed on apple arm64, and `J` was read with `ldrb`. it was verified by assembling with llvm-mc for darwin, then rewriting to linux with sed and running under qemu, where it matched the vm byte for byte. this version couldn't handle self-modifying code.
-
-### 5
-
-> can you write the assembler in assembly and pair the code with the assembly and the assembler so that it can re-wire itself at runtime? or maybe include an interp for self-modification, otherwise fns are exec'd native
-
-**summary:** both. `aslib.s` is the assembler written in vm assembly as a library (`a.src`, `a.dst`, `a.org`, `a.lab`, then `a.run c`), and with `asmain.s` it's a 568-word standalone assembler. it's byte-identical to the c assembler and reaches a fixpoint when assembling itself. this is also where the `:;comment` trick came from. the compiler became a hybrid. the image is split into basic blocks, each block counts how many of its words differ from the original image, and stores into code go through a slow path that updates the counters. clean blocks run native, while dirty blocks and anything past the image run in an interpreter compiled into the same binary, which hands control back to native code whenever it lands in a clean block. `cc -t` printed `[` and `]` at each switch. the demo rewired a function at runtime to point at freshly assembled code and then restored it.
-
-### 6
-
-> which one do you like more? and which is more performant?
-> keep that one, then port to using llvm ir so that it can be platform independent
-
-**summary:** no reply was sent at the time. the llvm port was dropped when message 7 changed direction. the question was answered at the start of the reply to message 7: the hybrid was the favorite because it kept the vm's semantics exact. the plain compiler was slightly faster on code that never rewrites itself, but it silently ran stale code once anything did. the zygote beats both.
-
-### 7
-
-> can you make it a zygote approach?
-> a minimal core loads first, reads and compiles the program (either wholly or partially, for speed) and replaces it with itself. so it's like a jvm but self-contained and on the same level as itself, where the assembler can itself be modified in-memory to assemble different code. for this we need to ditch llvm
-
-**summary:** the current design, described above. the native core only does jit and glue. it loads the genome (boot loader + assembler, both vm code) at 48000. the boot loader reads source until eof or a backtick, assembles it at 0 and jumps there. compilation is lazy per block, straight from live memory, with superblocks through `z`, fused constant targets, and dispatch chained through a table. invalidation uses the coverage byte map plus a store hook, so self-modified code gets retranslated instead of interpreted. the resident assembler sits at fixed addresses, and `repl.s` rewrites its mnemonic table so `p` means out, then assembles and runs input chunks in a loop, overwriting the same address each time. verified under qemu on linux arm64, including the self-hosted assembler running on the zygote. about 4x faster than the golfed interpreter.
-
-### 8
-
-> damn dawg you made an entire baremetal os?
->
-> let's move to cowork. pack everything up alongside a transcript of every message i've sent and your own notes on what you've implemented in an md file, with the code, so i can move it.
-
-**summary:** not quite an os. it's a userland runtime. it still runs on darwin or linux, uses their mmap for executable memory, and gets i/o from the host through `Q`, `G` and `J`. there's no scheduler, no drivers and no hardware access, so a closer description is a tiny self-hosting jit, in the spirit of a jvm. the first packing attempt bundled every stage's code with a full test script.
-
-### 9
-
-> nono we don't need all the code, just the recent ones. my msgs and your summaries are more important and all should be there
-
-**summary:** this package. only the zygote stack plus its examples and tests are included, and earlier stages appear only as summaries. the golfed reference vm and assembler are inlined under message 3, because they define the instruction set.
diff --git a/docs/history/07-zygote/code/aslib.s b/docs/history/07-zygote/code/aslib.s
deleted file mode 100644
index fc36df8..0000000
--- a/docs/history/07-zygote/code/aslib.s
+++ /dev/null
@@ -1,67 +0,0 @@
-:;assembler_library
-:;in:_a.src_source_text_one_char_per_word_zero_terminated
-:;in:_a.dst_output_address_a.org_address_the_code_will_run_at_a.lab_label_scratch
-:;out:_a.n_words_written
-:a.run 0 a.pass ! 0 a.nl !
-:a.p0 a.src @ a.q ! 0 a.n !
-:a.lp a.skip c a.q @ @ 0 $ a.c ! a.pd z
- a.c @ 34 - a.str z
- a.c @ 58 - a.lbl z
- a.q @ 1 + @ a.end c a.ch z
- a.c @ a.op c 0 $ -1 ~ < a.ch1 z
- 2 * a.emit c a.q @ 1 + a.q ! a.lp j
-:a.ch1 _
-:a.ch a.c @ 35 - a.raw z
- a.q @ a.val c 2 * 1 + a.emit c a.tend c a.lp j
-:a.raw a.q @ 1 + a.val c a.emit c a.tend c a.lp j
-:a.str a.q @ 1 + a.q !
-:a.s1 a.q @ @ 0 $ a.end c a.s2 z _ a.lp j
-:a.s2 a.emit c a.q @ 1 + a.q ! a.s1 j
-:a.lbl a.pass @ a.l1 z a.tend c a.lp j
-:a.l1 a.q @ 1 + a.lab @ a.nl @ 2 * + !
- a.org @ a.n @ + a.lab @ a.nl @ 2 * + 1 + !
- a.nl @ 1 + a.nl ! a.tend c a.lp j
-:a.pd a.pass @ a.p1 z r
-:a.p1 1 a.pass ! a.p0 j
-
-:;(c_--_f)_true_for_whitespace_and_the_terminator
-:a.end 0 $ 33 < ~ -1 ~ < & r
-:;skip_whitespace_at_a.q
-:a.skip a.q @ @ 0 $ a.sk1 z a.end c a.sk2 z a.q @ 1 + a.q ! a.skip j
-:a.sk1 _
-:a.sk2 r
-:;move_a.q_to_the_end_of_the_token
-:a.tend a.q @ @ a.end c a.te1 z r
-:a.te1 a.q @ 1 + a.q ! a.tend j
-:;(w_--)
-:a.emit a.dst @ a.n @ + ! a.n @ 1 + a.n ! r
-:;(x_y_--_f)_compare_two_tokens
-:a.eq a.y ! a.x !
-:a.e1 a.x @ @ a.end c a.y @ @ a.end c 1 $ 1 $ & a.e2 z _ _ 1 r
-:a.e2 | a.e3 z 0 r
-:a.e3 a.x @ @ a.y @ @ - a.e4 z 0 r
-:a.e4 a.x @ 1 + a.x ! a.y @ 1 + a.y ! a.e1 j
-:;(c_--_index_or_-1)
-:a.op a.c ! 0 a.j !
-:a.o1 a.ops a.j @ + @ 0 $ a.o2 z a.c @ - a.o3 z a.j @ 1 + a.j ! a.o1 j
-:a.o2 _ -1 r
-:a.o3 a.j @ r
-:;(q_--_v)_last_matching_label_else_decimal
-:a.val a.t ! a.nl @ a.j !
-:a.v1 a.j @ a.vn z a.j @ 1 - a.j !
- a.lab @ a.j @ 2 * + @ a.t @ a.eq c a.v1 z
- a.lab @ a.j @ 2 * + 1 + @ r
-:a.vn a.t @ a.num j
-:;(q_--_v)_like_atoi
-:a.num a.x ! 0 a.neg ! 0 a.v !
- a.x @ @ 45 - a.n1 z a.x @ @ 43 - a.n2 z a.n3 j
-:a.n1 1 a.neg !
-:a.n2 a.x @ 1 + a.x !
-:a.n3 a.x @ @ 48 - 0 $ 10 < 1 $ -1 ~ < & a.n4 z
- a.v @ 10 * + a.v ! a.x @ 1 + a.x ! a.n3 j
-:a.n4 _ a.v @ a.neg @ a.n5 z 0 ~ -
-:a.n5 r
-
-:a.src #0 :a.dst #0 :a.org #0 :a.lab #0 :a.n #0 :a.pass #0 :a.nl #0 :a.q #0
-:a.c #0 :a.j #0 :a.t #0 :a.v #0 :a.neg #0 :a.x #0 :a.y #0
-:a.ops "q@!$_~cjzr,?.+-*&|^>< #0
diff --git a/docs/history/07-zygote/code/core.c b/docs/history/07-zygote/code/core.c
deleted file mode 100644
index 41930f4..0000000
--- a/docs/history/07-zygote/code/core.c
+++ /dev/null
@@ -1,276 +0,0 @@
-/* zygote core: puts the genome (boot loader + assembler, both vm code) into
- memory and runs it. every block of vm code is translated to arm64 the first
- time it is reached, straight from live memory. stores that land on translated
- words throw those translations away, so code that rewrites itself, including
- the resident assembler, just gets retranslated on the next visit.
- arm64 only (darwin or linux). ZYTRACE=1 logs translations on stderr. */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/mman.h>
-#ifdef __APPLE__
-#include <pthread.h>
-#include <libkern/OSCacheControl.h>
-#endif
-#include "genome.h"
-
-#ifndef JTYPE
-#define JTYPE char
-#endif
-char Q(void);
-void G(char);
-extern JTYPE J;
-
-#define MW 65536
-#define CODEW (4 << 20) /* 16 MB of code, in instruction words */
-#define MAXB 65536
-#define BLKW 1024 /* longest block, in vm words */
-
-static int m[MW];
-static unsigned char cov[MW]; /* 1 if a live translation covers the word */
-static void *tab[MW]; /* vm address -> translation */
-static struct { int a, b; } bl[MAXB]; /* live translations cover [a,b); a < 0 is dead */
-static int nbl, trace;
-static unsigned *code, *cp, *x_enter, *x_exit, *x_disp, *x_first;
-static long S[9]; /* m, s, r, tab, cov, Q, G, &J, store hook; read by the stubs */
-
-enum { W0 = 0, W1 = 1, X9 = 9, X10, X11, M = 19, SP = 20, RP = 21, TAB = 22, ST = 23 };
-
-static void E(unsigned x) { *cp++ = x; }
-static void li(int d, int v)
-{
- unsigned u = (unsigned)v;
- E(0x52800000 | (u & 0xffff) << 5 | d);
- if (u >> 16) E(0x72a00000 | (u >> 16) << 5 | d);
-}
-static void ldr_post(int t, int n, int i) { E(0xb8400400 | (i & 0x1ff) << 12 | n << 5 | t); }
-static void ldr_pre(int t, int n, int i) { E(0xb8400c00 | (i & 0x1ff) << 12 | n << 5 | t); }
-static void str_pre(int t, int n, int i) { E(0xb8000c00 | (i & 0x1ff) << 12 | n << 5 | t); }
-static void str_post(int t, int n, int i) { E(0xb8000400 | (i & 0x1ff) << 12 | n << 5 | t); }
-static void ldr_u(int t, int n, int o) { E(0xb9400000 | (o / 4) << 10 | n << 5 | t); }
-static void str_u(int t, int n, int o) { E(0xb9000000 | (o / 4) << 10 | n << 5 | t); }
-static void ldrx_u(int t, int n, int o) { E(0xf9400000 | (o / 8) << 10 | n << 5 | t); }
-static void ldr_rr(int t, int n, int r, int ext) { E(0xb8600800 | r << 16 | ext << 13 | 1 << 12 | n << 5 | t); }
-static void str_rr(int t, int n, int r, int ext) { E(0xb8200800 | r << 16 | ext << 13 | 1 << 12 | n << 5 | t); }
-static void ldp_post(int t, int u, int n, int i) { E(0x28c00000 | ((i / 4) & 0x7f) << 15 | u << 10 | n << 5 | t); }
-static void op3(unsigned op, int d, int n, int r) { E(op | r << 16 | n << 5 | d); }
-static void addx_ext(int d, int n, int r, int ext) { E(0x8b200000 | r << 16 | ext << 13 | 2 << 10 | n << 5 | d); }
-static void movw(int d, int r) { E(0x2a0003e0 | r << 16 | d); }
-static void blr(int n) { E(0xd63f0000 | n << 5); }
-static void b_to(unsigned *t) { long o = t - cp; E(0x14000000 | (o & 0x3ffffff)); }
-static void cb_fix(unsigned *at, unsigned *to) { *at |= ((to - at) & 0x7ffff) << 5; }
-#define SXTW 6
-#define UXTW 2
-
-static void build_stubs(void)
-{
- cp = code;
- x_enter = cp; /* int enter(long *S, void *code) */
- E(0xa9bc7bfd); /* stp x29, x30, [sp, #-64]! */
- E(0x910003fd); /* mov x29, sp */
- E(0xa90153f3); /* stp x19, x20, [sp, #16] */
- E(0xa9025bf5); /* stp x21, x22, [sp, #32] */
- E(0xf9001bf7); /* str x23, [sp, #48] */
- E(0xaa0003f7); /* mov x23, x0 */
- ldrx_u(M, ST, 0);
- ldr_u(X9, ST, 8); addx_ext(SP, M, X9, UXTW);
- ldr_u(X9, ST, 16); addx_ext(RP, M, X9, UXTW);
- ldrx_u(TAB, ST, 24);
- E(0xd61f0020); /* br x1 */
- x_exit = cp; /* w0 = next vm address */
- op3(0xcb000000, X9, SP, M); E(0xd342fd29); str_u(X9, ST, 8); /* s = (x20 - x19) >> 2 */
- op3(0xcb000000, X9, RP, M); E(0xd342fd29); str_u(X9, ST, 16);
- E(0xa94153f3); /* ldp x19, x20, [sp, #16] */
- E(0xa9425bf5); /* ldp x21, x22, [sp, #32] */
- E(0xf9401bf7); /* ldr x23, [sp, #48] */
- E(0xa8c47bfd); /* ldp x29, x30, [sp], #64 */
- E(0xd65f03c0); /* ret */
- x_disp = cp; /* w0 = target: chain through tab[] or fall back to c */
- E(0x7140401f); /* cmp w0, #65536 */
- E(0x54000002 | ((x_exit - cp) & 0x7ffff) << 5); /* b.hs exit */
- E(0xf8605ac9); /* ldr x9, [x22, w0, uxtw #3] */
- E(0xb4000009 | ((x_exit - cp) & 0x7ffff) << 5); /* cbz x9, exit */
- E(0xd61f0120); /* br x9 */
- x_first = cp;
-}
-
-static void flush(void)
-{
- memset(tab, 0, sizeof tab);
- memset(cov, 0, sizeof cov);
- nbl = 0;
- cp = x_first;
- if (trace) fprintf(stderr, "jit: flushed\n");
-}
-
-static void jitwrite(int on)
-{
-#ifdef __APPLE__
- pthread_jit_write_protect_np(!on);
-#else
- (void)on;
-#endif
-}
-
-static void syncicache(unsigned *a, unsigned *b)
-{
-#ifdef __APPLE__
- sys_icache_invalidate(a, (char *)b - (char *)a);
-#else
- __builtin___clear_cache((char *)a, (char *)b);
-#endif
-}
-
-/* store hook for words that are covered by a translation */
-static int hook(int a, int v)
-{
- int j, x, hit = 0;
- if (m[a] == v) return 0;
- m[a] = v;
- for (j = 0; j < nbl; j++)
- if (bl[j].a >= 0 && bl[j].a <= a && a < bl[j].b) {
- tab[bl[j].a] = 0;
- for (x = bl[j].a; x < bl[j].b; x++) cov[x] = 0;
- if (trace) fprintf(stderr, "jit: dropped %d..%d\n", bl[j].a, bl[j].b - 1);
- bl[j].a = -1;
- hit = 1;
- }
- for (j = 0; j < nbl; j++)
- if (bl[j].a >= 0) for (x = bl[j].a; x < bl[j].b; x++) cov[x] = 1;
- return hit;
-}
-
-static void op(int i, int k) /* everything except control flow */
-{
- unsigned *a, *b, *c, *z;
- switch (k) {
- case 1: ldr_u(X9, SP, 0); ldr_rr(X9, M, X9, SXTW); str_u(X9, SP, 0); return;
- case 2: /* store: inline unless the target word is translated */
- ldp_post(X9, X10, SP, 8);
- E(0x7140413f); /* cmp w9, #65536 */
- a = cp; E(0x54000002); /* b.hs done */
- ldrx_u(X11, ST, 32);
- E(0x3869496b); /* ldrb w11, [x11, w9, uxtw] */
- b = cp; E(0x3500000b); /* cbnz w11, slow */
- str_rr(X10, M, X9, UXTW);
- c = cp; E(0x14000000); /* b done */
- cb_fix(b, cp); /* slow: hook(a, v) */
- movw(W0, X9); movw(W1, X10); ldrx_u(X11, ST, 64); blr(X11);
- z = cp; E(0x34000000); /* cbz w0, done */
- li(W0, i + 1); b_to(x_exit); /* a translation died: resume at i+1 in c */
- cb_fix(a, cp); cb_fix(z, cp);
- *c |= (cp - c) & 0x3ffffff;
- return;
- case 3: ldr_u(X9, SP, 0); E(0x9100128a); ldr_rr(X9, X10, X9, SXTW); str_u(X9, SP, 0); return; /* add x10, x20, #4 */
- case 4: E(0x91001294); return; /* add x20, x20, #4 */
- case 5: E(0x29402a89); E(0x2900268a); return; /* ldp w9, w10, [x20]; stp w10, w9, [x20] */
- case 10: ldrx_u(X11, ST, 40); blr(X11); E(0x13001c09); str_pre(X9, SP, -4); return; /* sxtb w9, w0 */
- case 11:
- ldrx_u(X9, ST, 56);
- if (sizeof J == 1) E(0x39400129); else ldr_u(X9, X9, 0); /* ldrb w9, [x9] */
- str_pre(X9, SP, -4);
- return;
- case 12: ldr_post(W0, SP, 4); E(0x13001c00); ldrx_u(X11, ST, 48); blr(X11); return; /* sxtb w0, w0 */
- }
- ldr_post(X9, SP, 4);
- ldr_u(X10, SP, 0);
- switch (k) {
- case 13: op3(0x0b000000, X10, X10, X9); break;
- case 14: op3(0x4b000000, X10, X10, X9); break;
- case 15: op3(0x1b007c00, X10, X10, X9); break;
- case 16: op3(0x0a000000, X10, X10, X9); break;
- case 17: op3(0x2a000000, X10, X10, X9); break;
- case 18: op3(0x4a000000, X10, X10, X9); break;
- case 19: op3(0x1ac02800, X10, X10, X9); break;
- default: E(0x6b09015f); E(0x1a9fa7ea); /* cmp w10, w9; cset w10, lt */
- }
- str_u(X10, SP, 0);
-}
-
-static void *translate(int p)
-{
- unsigned *start;
- int i = p, n = 0, w, k, t;
- if (cp - code > CODEW - 65536 || nbl >= MAXB) flush();
- start = cp;
- for (;;) {
- if (i >= MW) { E(0x12800000); b_to(x_exit); break; } /* movn w0, #0: halt */
- if (n++ >= BLKW) { li(W0, i); b_to(x_disp); break; }
- w = m[i];
- cov[i] = 1;
- if (w & 1 && i + 1 < MW && !(m[i + 1] & 1) && (k = m[i + 1] / 2) >= 6 && k <= 8) {
- t = (w - 1) / 2; /* literal + call/jmp/jz with a known target */
- cov[i + 1] = 1;
- if (k == 8) {
- unsigned *a;
- ldr_post(X10, SP, 4);
- a = cp; E(0x3500000a); /* cbnz w10, fallthrough */
- li(W0, t); b_to(x_disp);
- cb_fix(a, cp);
- i += 2;
- continue;
- }
- li(X10, i + 2);
- if (k == 6) str_post(X10, RP, 4); else str_u(X10, RP, 0);
- li(W0, t); b_to(x_disp);
- i += 2;
- break;
- }
- if (w & 1) { li(X9, (w - 1) / 2); str_pre(X9, SP, -4); i++; continue; }
- k = w / 2;
- if (k == 0) { E(0x12800000); b_to(x_exit); i++; break; }
- if (k < 0 || k == 6 || k == 7) {
- ldr_post(W0, SP, 4);
- li(X10, i + 1);
- if (k == 6) str_post(X10, RP, 4); else str_u(X10, RP, 0);
- if (k < 0) { li(X11, 7 - k); addx_ext(RP, RP, X11, SXTW); }
- b_to(x_disp);
- i++;
- break;
- }
- if (k == 8) { /* jz: taken leaves, not taken keeps going */
- ldp_post(X9, X10, SP, 8);
- E(0x3500006a); /* cbnz w10, +3 */
- movw(W0, X9);
- b_to(x_disp);
- i++;
- continue;
- }
- if (k == 9) { ldr_pre(W0, RP, -4); b_to(x_disp); i++; break; }
- op(i, k);
- i++;
- }
- bl[nbl].a = p;
- bl[nbl++].b = i;
- tab[p] = start;
- syncicache(start, cp);
- if (trace) fprintf(stderr, "jit: %d..%d -> %ld bytes\n", p, i - 1, (long)((char *)cp - (char *)start));
- return start;
-}
-
-int main(void)
-{
- int p = GORG;
- union { unsigned *u; int (*f)(long *, void *); } enter;
- trace = getenv("ZYTRACE") != 0;
-#ifdef __APPLE__
- code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0);
-#else
- code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-#endif
- if (code == MAP_FAILED) { perror("mmap"); return 1; }
- jitwrite(1);
- build_stubs();
- jitwrite(0);
- syncicache(code, cp);
- memcpy(m + GORG, genome, sizeof genome);
- S[0] = (long)m; S[1] = 65000; S[2] = 60000; S[3] = (long)tab; S[4] = (long)cov;
- S[5] = (long)Q; S[6] = (long)G; S[7] = (long)&J; S[8] = (long)hook;
- enter.u = x_enter;
- while ((unsigned)p < MW) {
- void *c = tab[p];
- if (!c) { jitwrite(1); c = translate(p); jitwrite(0); }
- p = enter.f(S, c);
- }
- return 0;
-}
diff --git a/docs/history/07-zygote/code/examples/asmain.s b/docs/history/07-zygote/code/examples/asmain.s
deleted file mode 100644
index 96d9658..0000000
--- a/docs/history/07-zygote/code/examples/asmain.s
+++ /dev/null
@@ -1,9 +0,0 @@
-:;standalone:_source_on_Q,_binary_on_G
-4000 a.q !
-:m.rd ? m.ch z 0 a.q @ ! m.go j
-:m.ch , a.q @ ! a.q @ 1 + a.q ! m.rd j
-:m.go 4000 a.src ! 40000 a.dst ! 0 a.org ! 50000 a.lab ! a.run c 0 a.j !
-:m.out a.j @ a.n @ < m.end z
- 40000 a.j @ + @ 0 $ . 0 $ 8 > . 0 $ 16 > . 24 > .
- a.j @ 1 + a.j ! m.out j
-:m.end q
diff --git a/docs/history/07-zygote/code/examples/echo.s b/docs/history/07-zygote/code/examples/echo.s
deleted file mode 100644
index 002935d..0000000
--- a/docs/history/07-zygote/code/examples/echo.s
+++ /dev/null
@@ -1 +0,0 @@
-:l ? x z q :x , . l j
diff --git a/docs/history/07-zygote/code/examples/fact.s b/docs/history/07-zygote/code/examples/fact.s
deleted file mode 100644
index 21f6e32..0000000
--- a/docs/history/07-zygote/code/examples/fact.s
+++ /dev/null
@@ -1,3 +0,0 @@
-10 fa c pb c 10 . q
-:fa 0 $ 2 < fb z _ 1 r :fb 0 $ 1 - fa c * r
-:pb 0 $ 1 > 0 $ p1 z pb c pd j :p1 _ :pd 1 & 48 + . r
diff --git a/docs/history/07-zygote/code/examples/hello.s b/docs/history/07-zygote/code/examples/hello.s
deleted file mode 100644
index e5baf1a..0000000
--- a/docs/history/07-zygote/code/examples/hello.s
+++ /dev/null
@@ -1,2 +0,0 @@
-s :l 0 $ @ 0 $ e z . 1 + l j :e q
-:s "hello, #32 "world! #10 #0
diff --git a/docs/history/07-zygote/code/examples/sm.s b/docs/history/07-zygote/code/examples/sm.s
deleted file mode 100644
index 954886e..0000000
--- a/docs/history/07-zygote/code/examples/sm.s
+++ /dev/null
@@ -1,3 +0,0 @@
-133 x ! :x 65 . 10 .
-fn c 133 fn ! fn c 131 fn ! fn c 10 . q
-:fn 65 . r
diff --git a/docs/history/07-zygote/code/gen.s b/docs/history/07-zygote/code/gen.s
deleted file mode 100644
index 8ec6aa2..0000000
--- a/docs/history/07-zygote/code/gen.s
+++ /dev/null
@@ -1,9 +0,0 @@
-:;genome_header,_fixed_addresses_starting_at_48000
-:;48000_boot__48002_a.run__48004.._&a.src_&a.dst_&a.org_&a.lab_&a.n_&a.ops
-z.boot j a.run j #a.src #a.dst #a.org #a.lab #a.n #a.ops
-:;boot:_read_source_until_eof_or_backtick,_assemble_it_at_0,_become_it
-:z.boot 20000 a.q !
-:z.rd ? z.ch z z.go j
-:z.ch , 0 $ 96 - z.end z a.q @ ! a.q @ 1 + a.q ! z.rd j
-:z.end _
-:z.go 0 a.q @ ! 20000 a.src ! 0 a.dst ! 0 a.org ! 45000 a.lab ! a.run c 0 j
diff --git a/docs/history/07-zygote/code/genome.h b/docs/history/07-zygote/code/genome.h
deleted file mode 100644
index abf6072..0000000
--- a/docs/history/07-zygote/code/genome.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#define GORG 48000
-#define GLEN 547
-static const int genome[] = {
-96021,14,96115,14,48510,48511,48512,48513,48514,48525,40001,97035,4,22,96037,16,
-96075,14,20,1,6,193,28,96073,16,97035,2,4,97035,2,3,26,
-97035,4,96027,14,8,1,97035,2,4,40001,97021,4,1,97023,4,1,
-97025,4,90001,97027,4,96115,12,1,14,1,97031,4,1,97033,4,97021,
-2,97035,4,1,97029,4,96503,12,97035,2,2,1,6,97037,4,96463,
-16,97037,2,69,28,96317,16,97037,2,117,28,96373,16,97035,2,3,
-26,2,96483,12,96253,16,97037,2,96713,12,1,6,-1,10,40,96251,
-16,5,30,96577,12,97035,2,3,26,97035,4,96141,14,8,97037,2,
-71,28,96293,16,97035,2,96779,12,5,30,3,26,96577,12,96545,12,
-96141,14,97035,2,3,26,96779,12,96577,12,96545,12,96141,14,97035,2,
-3,26,97035,4,97035,2,2,1,6,96483,12,96353,16,8,96141,14,
-96577,12,97035,2,3,26,97035,4,96329,14,97031,2,96389,16,96545,12,
-96141,14,97035,2,3,26,97027,2,97033,2,5,30,26,4,97025,2,
-97029,2,26,97027,2,97033,2,5,30,26,3,26,4,97033,2,3,
-26,97033,4,96545,12,96141,14,97031,2,96473,16,18,3,97031,4,96127,
-14,1,6,67,40,10,-1,10,40,32,18,97035,2,2,1,6,
-96541,16,96483,12,96543,16,97035,2,3,26,97035,4,96503,14,8,18,
-97035,2,2,96483,12,96561,16,18,97035,2,3,26,97035,4,96545,14,
-97023,2,97029,2,26,4,97029,2,3,26,97029,4,18,97049,4,97047,
-4,97047,2,2,96483,12,97049,2,2,96483,12,3,6,3,6,32,
-96653,16,8,8,3,18,34,96663,16,1,18,97047,2,2,97049,2,
-2,28,96685,16,1,18,97047,2,3,26,97047,4,97049,2,3,26,
-97049,4,96611,14,97037,4,1,97039,4,97051,97039,2,26,2,1,6,
-96767,16,97037,2,28,96773,16,97039,2,3,26,97039,4,96723,14,8,
--1,18,97039,2,18,97041,4,97033,2,97039,4,97039,2,96861,16,97039,
-2,3,28,97039,4,97027,2,97039,2,5,30,26,2,97041,2,96603,
-12,96791,16,97027,2,97039,2,5,30,26,3,26,2,18,97041,2,
-96869,14,97047,4,1,97045,4,1,97043,4,97047,2,2,91,28,96917,
-16,97047,2,2,87,28,96923,16,96935,14,3,97045,4,97047,2,3,
-26,97047,4,97047,2,2,97,28,1,6,21,40,3,6,-1,10,
-40,32,96999,16,97043,2,21,30,26,97043,4,97047,2,3,26,97047,
-4,96935,14,8,97043,2,97045,2,97019,16,1,10,28,18,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,113,64,33,
-36,95,126,99,106,122,114,44,63,46,43,45,42,38,124,94,
-62,60,0
-};
diff --git a/docs/history/07-zygote/code/io_linux.c b/docs/history/07-zygote/code/io_linux.c
deleted file mode 100644
index 7ea34e0..0000000
--- a/docs/history/07-zygote/code/io_linux.c
+++ /dev/null
@@ -1,6 +0,0 @@
-#include <stdio.h>
-int J;
-static int pk(void){int c=getchar();ungetc(c,stdin);return c==EOF;}
-char Q(void){int c=getchar();J=pk();return c;}
-void G(char c){putchar(c);}
-__attribute__((constructor)) static void init(void){J=pk();}
diff --git a/docs/history/07-zygote/code/mkgenome.c b/docs/history/07-zygote/code/mkgenome.c
deleted file mode 100644
index 7d9f0bc..0000000
--- a/docs/history/07-zygote/code/mkgenome.c
+++ /dev/null
@@ -1,35 +0,0 @@
-/* assembles the genome at a fixed origin and writes it as a c header */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#define ORG 48000
-static char tok[30000][99];
-static int lab[30000], nt, img[17536], n;
-static char *ops = "q@!$_~cjzr,?.+-*&|^><";
-static int val(char *s)
-{
- int j; char *e; long v;
- for (j = nt; j--;) if (tok[j][0] == ':' && !strcmp(tok[j] + 1, s)) return lab[j];
- v = strtol(s, &e, 0);
- if (!*s || *e) { fprintf(stderr, "unknown label %s\n", s); exit(1); }
- return (int)v;
-}
-static void put(int w) { if (n >= 17536) exit(1); img[n++] = w; }
-int main(void)
-{
- int pass, i; char *q, *c;
- while (nt < 30000 && scanf("%98s", tok[nt]) == 1) nt++;
- for (pass = 0; pass < 2; pass++)
- for (n = i = 0; i < nt; i++) {
- q = tok[i];
- if (*q == '"') while (*++q) put(*q);
- else if (*q == ':') lab[i] = ORG + n;
- else if (!q[1] && (c = strchr(ops, *q))) put((c - ops) * 2);
- else if (*q == '#') put(pass ? val(q + 1) : 0);
- else put(pass ? val(q) * 2 + 1 : 1);
- }
- printf("#define GORG %d\n#define GLEN %d\nstatic const int genome[] = {", ORG, n);
- for (i = 0; i < n; i++) printf("%s%d", !i ? "\n" : i % 16 ? "," : ",\n", img[i]);
- printf("\n};\n");
- return 0;
-}
diff --git a/docs/history/07-zygote/code/repl.s b/docs/history/07-zygote/code/repl.s
deleted file mode 100644
index 35ab7d4..0000000
--- a/docs/history/07-zygote/code/repl.s
+++ /dev/null
@@ -1,11 +0,0 @@
-:;teach_the_resident_assembler_that_p_means_out
-112 48009 @ 12 + !
-:;each_backtick-separated_chunk_of_input_is_assembled_at_40000_and_called
-:nx ? rd0 z q
-:rd0 30000 ptr !
-:rd ? ch z go j
-:ch , 0 $ 96 - eos z ptr @ ! ptr @ 1 + ptr ! rd j
-:eos _
-:go 0 ptr @ ! 30000 48004 @ ! 40000 48005 @ ! 40000 48006 @ ! 41000 48007 @ ! 48002 c
- 40000 c nx j
-:ptr #0
diff --git a/docs/history/07-zygote/code/test.sh b/docs/history/07-zygote/code/test.sh
deleted file mode 100755
index add0379..0000000
--- a/docs/history/07-zygote/code/test.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-# builds the zygote for arm64 linux and runs it under qemu.
-# needs gcc, aarch64-linux-gnu-gcc and qemu-aarch64. on an arm64 mac or linux box,
-# build natively instead and drop the qemu prefix.
-cd "$(dirname "$0")"; T=$(mktemp -d); Q="timeout 60 qemu-aarch64"
-chk(){ if [ "$1" = "$2" ]; then echo "ok $3"; else echo "FAIL $3: got [$1]"; fi; }
-gcc -w mkgenome.c -o $T/mkg && cat gen.s aslib.s | $T/mkg >$T/g.h
-cmp -s $T/g.h genome.h && echo "ok genome.h is up to date" || echo "FAIL genome.h is stale, regenerate it"
-aarch64-linux-gnu-gcc -O2 -w -DJTYPE=int -static core.c io_linux.c -o $T/zy || exit 1
-chk "$($Q $T/zy <examples/hello.s)" "hello, world!" "hello"
-chk "$($Q $T/zy <examples/fact.s)" "1101110101111100000000" "recursive factorial"
-chk "$( (cat examples/echo.s; printf '`abc\n') | $Q $T/zy)" "abc" "echo after the backtick"
-chk "$($Q $T/zy <examples/sm.s | tr '\n' '/')" "B/ABA/" "self-patching"
-cat examples/asmain.s aslib.s >$T/asm.s
-(cat $T/asm.s; printf '`'; cat examples/hello.s) | $Q $T/zy >$T/a
-chk "$(od -An -td4 $T/a | tr -s ' \n' ' ' | sed 's/^ //;s/ $//')" "29 1 6 2 1 6 27 16 24 3 26 3 14 0 104 101 108 108 111 44 32 119 111 114 108 100 33 10 0" "self-hosted assembler, running on the zygote, assembles hello"
-chk "$( (cat repl.s; printf '`104 p 105 p 10 p r`121 p 111 p 10 p r') | $Q $T/zy | tr '\n' '/')" "hi/yo/" "repl with mutated assembler"
-rm -rf $T