Files
AILang/examples/rc_list_drop.ail.json
T
Brummel 298dcaf0f7 Iter 18c.4: per-type drop fn + recursive dec cascade
Closes 18c.3's main scope cut. Codegen now emits a per-ADT and
per-closure drop function under --alloc=rc; the Term::Let
scope-close emission routes through these drops instead of
ailang_rc_dec directly, so a recursive ADT (List, Tree) under
--alloc=rc actually frees its tail cells when the outer binder
is dec'd.

Drop-symbol scheme:

- For every Def::Type T in module m: emit one
  define void @drop_<m>_<T>(ptr %p) under --alloc=rc.
  Body: null-guard, load tag, switch on tag, per-ctor arm dec's
  every pointer-typed field via field_drop_call, then dec's the
  outer cell and rets.

- For every escaping Term::Lam: emit drop_<m>_<lam>_env(ptr) for
  the captured-free-vars block + drop_<m>_<lam>_pair(ptr) for the
  {env, fn-ptr} closure pair. A new closure_drops side table on
  Emitter keys closure-pair SSAs to their pair-drop symbol.

Decision (preempted in dispatch): always emit @drop_<m>_<T> for
every ADT, even ones with no boxed children. The body for
no-boxed-children ADTs is null-guard + dec + ret. Call sites are
uniform; future codegen changes that thread cleanup through the
drop seam (atomic dec under threading, etc.) have one canonical
entry per type.

field_drop_call dispatches on field type:
- Type::Con { name, .. } → drop_<owner>_<name> (recursion).
- Type::Str / Type::Fn / Type::Var → fall back to ailang_rc_dec.
  These three fall-backs are 18d/18e/monomorphisation debt; they
  are flagged in-source for grep.

Term::Let scope-close emission is the same as 18c.3 modulo the
target: Term::Ctor binders → drop_<owner>_<type>; Term::Lam
binders → the closure_drops-recorded pair-drop symbol. All
other emission gates from 18c.3 (consume_count == 0,
body-tail-not-binder, block-not-terminated, non-escape) are
unchanged.

Recursion is stack-recursive — drop_IntList's Cons arm calls
itself on the tail. For 18c.4's 5-element fixtures the depth is
safe; long lists are out of scope until 18e replaces the
recursive call with a worklist allocator. The recursive site is
commented for grep.

Tests:
- examples/rc_list_drop.ail.json — 5-element IntList summed via
  sum_list (xs has consume_count == 1; codegen skips the let-
  close drop call but the IR shape is locked in by the unit test
  below).
- examples/rc_list_drop_borrow.ail.json — same 5-element IntList,
  match-only consumption (consume_count == 0). Codegen emits
  drop_<m>_IntList(xs) at scope close; the cascade runs over all
  5 cells at runtime.
- crates/ail/tests/e2e.rs — alloc_rc_recursive_list_sum +
  alloc_rc_borrow_only_recursive_list_drop. Both assert
  --alloc=rc stdout matches --alloc=gc and the binaries exit
  cleanly.
- crates/ailang-codegen/src/lib.rs — unit test asserting the IR
  shape: define void @drop_rclist_IntList header, recursive
  self-call inside, outer ailang_rc_dec at the tail; negative-
  complement under --alloc=gc.

Tests: E2E 53 -> 55, codegen unit 11 -> 12. cargo test
--workspace green.
2026-05-08 10:37:25 +02:00

140 lines
4.1 KiB
JSON

{
"schema": "ailang/v0",
"name": "rc_list_drop",
"imports": [],
"defs": [
{
"kind": "type",
"name": "IntList",
"doc": "Recursive Int list. Iter 18c.4 RC fixture: codegen emits `drop_rc_list_drop_IntList` whose `Cons` arm recursively calls itself on the tail field.",
"ctors": [
{ "name": "Nil", "fields": [] },
{
"name": "Cons",
"fields": [
{ "k": "con", "name": "Int" },
{ "k": "con", "name": "IntList" }
]
}
]
},
{
"kind": "fn",
"name": "sum_list",
"type": {
"k": "fn",
"params": [{ "k": "con", "name": "IntList" }],
"ret": { "k": "con", "name": "Int" },
"effects": []
},
"params": ["xs"],
"doc": "Recursive fold over IntList.",
"body": {
"t": "match",
"scrutinee": { "t": "var", "name": "xs" },
"arms": [
{
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
"body": { "t": "lit", "lit": { "kind": "int", "value": 0 } }
},
{
"pat": {
"p": "ctor",
"ctor": "Cons",
"fields": [
{ "p": "var", "name": "h" },
{ "p": "var", "name": "t" }
]
},
"body": {
"t": "app",
"fn": { "t": "var", "name": "+" },
"args": [
{ "t": "var", "name": "h" },
{
"t": "app",
"fn": { "t": "var", "name": "sum_list" },
"args": [{ "t": "var", "name": "t" }]
}
]
}
}
]
}
},
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": ["IO"]
},
"params": [],
"doc": "Build a 5-element IntList [1,2,3,4,5] and print its sum (15). Under --alloc=rc the recursive drop_rc_list_drop_IntList fn cascades through the tail at process exit when sum_list returns ownership-implicit (the consume_count of `xs` is 1 so no dec at the outer let; the test's correctness invariant is the byte-identical stdout — leak quantification is 18f's bench).",
"body": {
"t": "let",
"name": "xs",
"value": {
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 1 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 2 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 3 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 4 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Cons",
"args": [
{ "t": "lit", "lit": { "kind": "int", "value": 5 } },
{
"t": "ctor",
"type": "IntList",
"ctor": "Nil",
"args": []
}
]
}
]
}
]
}
]
}
]
},
"body": {
"t": "do",
"op": "io/print_int",
"args": [
{
"t": "app",
"fn": { "t": "var", "name": "sum_list" },
"args": [{ "t": "var", "name": "xs" }]
}
]
}
}
}
]
}