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.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"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" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "rc_list_drop_borrow",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "IntList",
|
||||
"doc": "Recursive Int list. Iter 18c.4 RC fixture: codegen emits a recursive drop fn; this fixture keeps the binder consume_count at 0 so the drop call actually fires at scope close, exercising the recursive cascade at runtime over a 5-element list.",
|
||||
"ctors": [
|
||||
{ "name": "Nil", "fields": [] },
|
||||
{
|
||||
"name": "Cons",
|
||||
"fields": [
|
||||
{ "k": "con", "name": "Int" },
|
||||
{ "k": "con", "name": "IntList" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [],
|
||||
"ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"doc": "Build a 5-element IntList and print only its head via match. xs has consume_count == 0 (only the match scrutinee, a borrow), so under --alloc=rc the let scope close emits `drop_rc_list_drop_borrow_IntList(xs)` which recursively dec-cascades over the whole 5-cell chain.",
|
||||
"body": {
|
||||
"t": "let",
|
||||
"name": "xs",
|
||||
"value": {
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Cons",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 11 } },
|
||||
{
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Cons",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 22 } },
|
||||
{
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Cons",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 33 } },
|
||||
{
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Cons",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 44 } },
|
||||
{
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Cons",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 55 } },
|
||||
{
|
||||
"t": "ctor",
|
||||
"type": "IntList",
|
||||
"ctor": "Nil",
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"body": {
|
||||
"t": "match",
|
||||
"scrutinee": { "t": "var", "name": "xs" },
|
||||
"arms": [
|
||||
{
|
||||
"pat": { "p": "ctor", "ctor": "Nil", "fields": [] },
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{ "t": "lit", "lit": { "kind": "int", "value": 0 } }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"pat": {
|
||||
"p": "ctor",
|
||||
"ctor": "Cons",
|
||||
"fields": [
|
||||
{ "p": "var", "name": "h" },
|
||||
{ "p": "var", "name": "t" }
|
||||
]
|
||||
},
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [{ "t": "var", "name": "h" }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user