diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /docs/history/10-zc/notes.md | |
a
Diffstat (limited to 'docs/history/10-zc/notes.md')
| -rw-r--r-- | docs/history/10-zc/notes.md | 37 |
1 files changed, 37 insertions, 0 deletions
diff --git a/docs/history/10-zc/notes.md b/docs/history/10-zc/notes.md new file mode 100644 index 0000000..96baec2 --- /dev/null +++ b/docs/history/10-zc/notes.md @@ -0,0 +1,37 @@ +# zc: the c-like genome, u m d and the collector + +code/ next to this file is the tree as shipped at the end of this stage (tinyvm-zc.zip), with its own README. + +## message 10 + +> we're gonna make this thing cooler. make the syntax C-like whilst still keeping the forth capabilities like super-homiconicity and the likes, add explicit and automatic gc (use a fast approach) and memory management that looks like this: +> every heap object has a classifier u m d, up middle down. up means the object will die at the scope above (becomes m when returned to the parent scope, and it must be returned and cannot be discarded). m means it must be used here and can be passed down (but once it's recasted as u or d it can't be casted back to m in this scope). d means the variable is passed down, and its lifetime ends after it's given for the first time to a scope lower than the current one. +> ask me questions to clarify this approach, then add it to the code and also add a simple static analyzer which checks these lifetimes + +my answers to the clarifying questions, exactly: + +> scope: every { } block +> +> lending an m object to a callee: borrow, no escape (Recommended) +> +> storing a heap object inside another object or a global: container gets ownership. putting a value inside a container requires its umd to match the parent, and from then on, the umd for that is also inherited from the parent (as in if a parent m is casted to d, so is the child heap object) +> +> gc: free lists + mark-sweep (Recommended) +> +> where the compiler lives: resident in vm code. bootstrap it step by step, by writing a c-like in core.c and then rewriting the compiler inside the c-like. also bring back llvm if you think it'll make your job easier +> +> homoiconic features: parse words, quoted code blocks, inline stack code, patchable compiler tables +> +> how an inner block hands a u object up: expression and assign, both +> +> when the analyzer runs: inside the compiler but can be turned off if the code requests it + +**summary:** the genome is now zc, a typeless c-like language, described above. stage 0 is `zc0.c` (kept as its own file next to core.c rather than inside it, so the runtime still has one native file), and the resident compiler is `src/compiler.zc` plus `src/rt.zc`, which reached a byte-identical fixpoint compiling itself. llvm wasn't brought back, since the target is the vm itself. memory went to 1m words, and core.c gained an interpreter for non-arm64 hosts. calls i made without asking: tags go on signatures (`fn f(m a, d b) -> u`) and recasts are `x as u` / `x as d`; a d object touched inside an inner block moves into that block; storing into a borrowed object, a global or untracked memory needs `as d`; handing a u up by assignment only reaches the directly enclosing block; unchecked code gives its objects to the collector. the old asm genome moved to `legacy/`. + +## working notes + +before asking anything i restated the model so it could be corrected early: m is owned here, lent freely and freed at the scope end; d is a move to the first lower scope it touches; u is a return obligation that arrives as m above; recasts only go away from m. the answers settled scopes (every block), lending (borrow, no escape), containment (tags must match and children inherit), the collector (free lists plus mark-sweep), where the compiler lives (resident, bootstrapped from c), the homoiconic features (all four), how blocks hand u up (value and assignment) and the analyzer (on, with an off switch). + +calls made without asking: memory grew to 1m words; core.c gained a plain interpreter for non-arm64 hosts; the language is typeless like b; tags went on signatures (`m p`, `d p`, `-> u`) with `x as u` casts; storing into globals or untracked memory needs d and hands the object to the collector; unchecked code is collector-managed. + +the bootstrap went zc0 (c) -> g0 -> g1 -> g2 and g1 == g2 on the first self-compile. things the analyzer caught in its own runtime on the way: puts walking a borrowed pointer with s++, wcopy returning a borrowed pointer, and the whole compiler source getting cut short by a backtick inside an error message (the backtick ends the source). the first round of rejection tests found one real gap, an m object unused before an early return, fixed with a path-independent "owned but never used" check, and one wrong test, where touching a d object in an else block moved it into that block, exactly as the rules say. |
