Iter 14f: Boehm conservative GC
Decision 9 ships. Through Iter 14e every ADT box, lambda env, and
closure pair was leaked. This iter substitutes GC_malloc for malloc
in all four IR allocation sites and links -lgc. No language change,
no AST change, no schema change.
Diff: 5 files modified, ~30 LOC net.
- codegen/lib.rs: 4 substitutions @malloc -> @GC_malloc.
- ail/main.rs: .arg("-lgc") in the clang invocation.
- 5 IR snapshot files: mechanical s/@malloc/@GC_malloc/, 9
occurrences. IR is bit-identical to pre-14f modulo this
substitution — exactly Decision 9's promise.
- e2e.rs: new test gc_handles_recursive_list_construction.
- examples/gc_stress.{ailx,ail.json}: new fixture, builds a 50-
element list via recursive Cons, sums it (1275).
Hash invariance verified: every existing fixture def hash
unchanged (codegen and link line are downstream of canonical
bytes; AST didn't move).
Tests 79 -> 80, all green. Existing 79 byte-identical stdout.
gc_stress -> 1275. list_map_poly -> 2/3/4 unchanged. sort
sorted-list unchanged. cargo doc 0 warnings.
GC notes (pertinent to future work):
- GC_INIT() not needed on Arch libgc 1.5.6 (auto-init via
__attribute__((constructor))).
- No conservative-scan over-retention observed.
- -lgc alone sufficient for link (pthread/dl transitive).
Pattern-shape note from gc_stress fixture writing: the post-14d
"if-then-else" replacement is `(match (app == n 0) (case
(pat-lit true) ...) (case (pat-wild) ...))`. Three lines for
what `if` used to do in one, but uniform with the language.
Worth flagging for the stdlib brief.
Language is feature-complete enough for stdlib. The three
blockers identified at the 14b boundary (redundancy 14d, tail
calls 14e, GC 14f) are all done. Plan 15a: first stdlib module
std_list.ailx with length/append/reverse/map/filter/fold_left/
fold_right/head/tail/is_empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2002,6 +2002,102 @@ Anything else (records as a primitive, nested patterns, local
|
||||
recursive let, type classes) can layer on later without forcing
|
||||
stdlib rewrites.
|
||||
|
||||
## Iter 14f — Boehm conservative GC
|
||||
|
||||
Decision 9 ships. Through Iter 14e every ADT box, lambda env,
|
||||
and closure pair was leaked. This iter substitutes
|
||||
`GC_malloc` for `malloc` in all four IR allocation sites and
|
||||
links `-lgc`. No language change, no AST change, no schema
|
||||
change.
|
||||
|
||||
**Diff: 5 files, ~30 LOC net.**
|
||||
|
||||
- `crates/ailang-codegen/src/lib.rs`: 4× `@malloc` → `@GC_malloc`
|
||||
(declare line + 3 call sites: `lower_ctor`, `lower_lambda`'s
|
||||
env, `lower_lambda`'s closure pair).
|
||||
- `crates/ail/src/main.rs`: `.arg("-lgc")` added to the clang
|
||||
invocation in `build_to`.
|
||||
- `crates/ail/tests/snapshots/{hello,sum,list,max3,ws_main}.ll`:
|
||||
mechanical s/@malloc/@GC_malloc/, 9 occurrences across 5
|
||||
files. The IR is bit-identical to pre-14f modulo this
|
||||
substitution — exactly Decision 9's promise.
|
||||
- `crates/ail/tests/e2e.rs`: new test
|
||||
`gc_handles_recursive_list_construction` (+19 LOC).
|
||||
- `examples/gc_stress.{ailx,ail.json}`: new fixture.
|
||||
|
||||
**Hash invariance verified.** Every existing fixture's def
|
||||
hashes are unchanged. The codegen and link line are downstream
|
||||
of canonical bytes; the AST schema didn't move; nothing on
|
||||
disk in `examples/*.ail.json` was touched. The new
|
||||
`gc_stress` module adds 4 new hashes, all unrelated.
|
||||
|
||||
**Tests: 80/80 (was 79).** Existing 79 produce byte-identical
|
||||
stdout — only the allocator changed, semantics unchanged. New:
|
||||
`gc_handles_recursive_list_construction` builds a List<Int> of
|
||||
length 50 via recursive `Cons`, sums it (`1275`). Manual smoke:
|
||||
|
||||
- `gc_stress.ail.json` → `1275`.
|
||||
- `list_map_poly` → `2 3 4` (unchanged).
|
||||
- `sort` → sorted list (unchanged).
|
||||
|
||||
`cargo doc --no-deps`: 0 warnings (DESIGN.md item 6 invariant
|
||||
preserved through nine iters of feature work).
|
||||
|
||||
**Pattern shape used in `gc_stress`** (caught a small typechecker
|
||||
constraint). The first proposed shape `(case (lit-int 0) Nil)`
|
||||
doesn't parse — `pat-lit` takes the bare literal token, not a
|
||||
keyword-prefixed form, and `case` requires a pattern. Worked
|
||||
shape: comparison-and-bool-match, mirroring `sort.ail.json`'s
|
||||
`<=` arm:
|
||||
|
||||
```
|
||||
(match (app == n 0)
|
||||
(case (pat-lit true) (term-ctor List Nil))
|
||||
(case (pat-wild) (term-ctor List Cons n (app build (app - n 1)))))
|
||||
```
|
||||
|
||||
This is the canonical "if-then-else" pattern post-14d. Worth
|
||||
flagging for the stdlib brief: predicates that need to branch
|
||||
go through the `==` / `<` / `<=` builtin returning Bool, then
|
||||
match on that Bool with a wildcard fallback. Three lines for
|
||||
what `if` used to do in one — but uniform with the rest of the
|
||||
language, no special case.
|
||||
|
||||
**GC integration notes.**
|
||||
- `GC_INIT()` is **not needed** on this build host (Arch
|
||||
with `libgc 1.5.6`). libgc auto-inits via
|
||||
`__attribute__((constructor))`.
|
||||
- No conservative-scan over-retention symptom observed: every
|
||||
existing test's stdout byte-identical; behaviour preserved.
|
||||
- `-lgc` alone is sufficient for the link; pthread/dl come in
|
||||
transitively from libgc.so's NEEDED entries.
|
||||
|
||||
**Language is feature-complete enough for stdlib.** Iters 14d
|
||||
(redundancy removal), 14e (explicit tail calls), 14f (GC) are
|
||||
the three blockers identified at the 14b boundary. They are
|
||||
all done. Anything else (records as primitive, nested patterns,
|
||||
local rec let, type classes) layers on later without forcing
|
||||
stdlib rewrite.
|
||||
|
||||
**Plan 15a.** First stdlib module: `examples/std/std_list.ailx`.
|
||||
Combinators: `length`, `append`, `reverse`, `map`, `filter`,
|
||||
`fold_left`, `fold_right`, `head`, `tail`, `is_empty`. Each
|
||||
combinator a fresh test vector for the parameterised-ADT +
|
||||
GC + tail-call combination. Authored in form (A) from day one;
|
||||
`.ail.json` produced via `ail parse`. Each combinator gets a
|
||||
dedicated e2e test.
|
||||
|
||||
If 15a surfaces compiler bugs (likely — every prior dogfood
|
||||
iter has, see 14a's monomorphisation bug), debugger handles
|
||||
them inline. If a compiler limitation surfaces that genuinely
|
||||
blocks the stdlib (e.g. nested patterns turn out to be needed),
|
||||
that becomes its own iter before 15a continues.
|
||||
|
||||
The architectural pin from Decision 6 governs: stdlib lives
|
||||
under `examples/std/` as `.ailx` source; tests load the
|
||||
generated `.ail.json`. `ailang-check` and `ailang-codegen`
|
||||
remain projection-agnostic.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user