1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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.
|