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:
@@ -526,6 +526,62 @@ constructor-blocked recursions in `map`, `sort`, `insert`
|
||||
remain unmarked — they cannot benefit from `musttail`
|
||||
without a source-level rewrite.
|
||||
|
||||
## Decision 9: memory management — Boehm conservative GC
|
||||
|
||||
Through Iter 14e, every ADT box, lambda env, and closure pair was
|
||||
allocated with bare `malloc` and never freed. That worked for the
|
||||
17 test fixtures (all small, all short-lived) but is incompatible
|
||||
with any real workload — a stdlib `fold` over a million-element
|
||||
list would leak a million boxes. The "Goal" section's "no GC for
|
||||
the MVP" framing predates the parameterised-ADT pipeline (Iter 13)
|
||||
and the explicit-recursion expectation (Iter 14e); both make a
|
||||
collector necessary.
|
||||
|
||||
**Choice: Boehm-Demers-Weiser conservative GC.** The simplest
|
||||
working option:
|
||||
|
||||
- Replace `malloc(...)` with `GC_malloc(...)` in every IR site
|
||||
(currently `lower_ctor`'s ADT box, `lower_lambda`'s env block
|
||||
and closure pair).
|
||||
- Replace the IR-level `declare ptr @malloc(i64)` with
|
||||
`declare ptr @GC_malloc(i64)`.
|
||||
- Add `-lgc` to the `clang` link command (in
|
||||
`crates/ail/src/main.rs`'s `Build` / `Run` paths).
|
||||
- No language-level change. No AST change. No schema change.
|
||||
|
||||
Rationale:
|
||||
|
||||
- **Mature.** Boehm has been the default conservative GC for
|
||||
decades. Linux distros ship it as `libgc` / `libgc-dev` /
|
||||
`gc` (Arch).
|
||||
- **No language work.** Conservative scan of the C stack handles
|
||||
AILang's stack frames without LLVM stack-map infrastructure
|
||||
(which is its own multi-iter design).
|
||||
- **Single-iter integration.** Lift-and-shift of the four
|
||||
allocation sites; all existing tests must still pass with
|
||||
identical output.
|
||||
|
||||
Trade-offs accepted:
|
||||
|
||||
- **Conservative over-retention.** A user-supplied `Int` field
|
||||
whose value happens to coincide with a heap address will pin
|
||||
that allocation. In practice, vanishingly rare for typical
|
||||
values; survivable.
|
||||
- **Pause time non-deterministic.** Boehm uses stop-the-world
|
||||
mark-sweep. For LLM-author-written stdlib code at MVP scale,
|
||||
pause times are not the bottleneck.
|
||||
- **Build-time dependency.** `libgc` must be installed on the
|
||||
build host. Users without it get a link-time error from
|
||||
clang, not a silent failure.
|
||||
|
||||
A future iter may layer a per-fn-arena optimisation on top: when
|
||||
a fn's return type contains no boxed ADT, ADT boxes allocated
|
||||
inside that fn cannot escape, so an arena freed at fn return is
|
||||
sound by construction (per the 14e GC notes). That requires
|
||||
escape analysis, the corresponding AST/IR plumbing, and is its
|
||||
own design pass. Boehm-everything is the floor; arena is an
|
||||
optimisation above it.
|
||||
|
||||
## Mangling scheme (Iter 5c)
|
||||
|
||||
All AILang functions are mangled to `@ail_<module>_<def>` — even in the
|
||||
|
||||
Reference in New Issue
Block a user