Files
AILang/examples/list_map_poly.ailx
T
Brummel d64031c234 Iter 14e: explicit, verified tail calls
Decision 8 ships. Term::App and Term::Do gain tail: bool with
serde-default false and skip-when-false serialisation. New
typecheck pass verify_tail_positions enforces tail-position rules
(Scheme-style propagation through match arms, seq.rhs, let body,
lam body). Codegen emits musttail call for marked App calls.

Hash invariance verified: only the two migrated print_list defs
(list_map_poly.print_list, sort.print_list) changed hashes; all
other defs across all 18 fixtures kept bit-identical hashes —
confirms the skip-when-false serialisation rule works.

Tests 76 -> 79: tail_call_in_non_tail_position_is_rejected,
tail_call_in_tail_position_is_accepted, plus an IR-grep e2e test
asserting that print_list's recursive call site emits musttail
in the lowered IR. Existing 25 e2e tests unchanged in behaviour
(map -> [2,3,4], sort -> sorted list).

IR evidence at the recursive site:
  %v7 = musttail call i8 @ail_list_map_poly_print_list(ptr %v6)
  ret i8 %v7

Two deviations called out in the implementer report and JOURNAL:
1. tail-do uses tail call, not musttail. Cross-type return
   (runtime helpers return i32, AILang Unit is i8) would have
   LLVM reject musttail. Path is implemented but not exercised
   by any current fixture; proper fix is runtime-helper signature
   change, punted.
2. block_terminated flag in codegen so tail-call emit
   (musttail call + ret) doesn't get a duplicate trailing ret
   from surrounding code (match-arm phi, fn-body, lambda thunk).
   Internal plumbing; required for IR well-formedness.

Form (A) productions now at ~30, exactly the constraint-1
budget. Future surface additions need to retire something or
explicit-budget-rebalance in DESIGN.md.

GC notes from implementer survey land in JOURNAL:
- Allocations cluster in lower_ctor; every term-ctor does
  malloc(8+8n).
- Tail recursion does not reduce alloc pressure, only stack.
  For map-style ctor-blocked recursions, allocation IS the
  bottleneck.
- Per-fn arena is sound only when fn return type contains no
  boxed ADT. Most current fixtures violate this.

Plan 14f: Boehm conservative GC (GC_malloc, -lgc) as a first
cut. Single-iter integration, no AST/schema change. Stress
test: build a 100k Cons list, observe RSS doesn't blow up.

After 14f the language is feature-complete enough for stdlib
work (15a).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 17:16:28 +02:00

63 lines
1.6 KiB
Plaintext

; Iter 14b design exhibit, finalised in Iter 14c.
; Hash-equivalent to list_map_poly.ail.json (round-trip-tested).
(module list_map_poly
(data List (vars a)
(doc "Polymorphic singly-linked list.")
(ctor Nil)
(ctor Cons a (con List a)))
(fn inc
(doc "Add 1 to an Int.")
(type (fn-type (params (con Int)) (ret (con Int))))
(params x)
(body (app + x 1)))
(fn map
(doc "Polymorphic map: apply f to every element, recursing on the tail.")
(type
(forall (vars a b)
(fn-type
(params (fn-type (params a) (ret b))
(con List a))
(ret (con List b)))))
(params f xs)
(body
(match xs
(case (pat-ctor Nil)
(term-ctor List Nil))
(case (pat-ctor Cons h t)
(term-ctor List Cons
(app f h)
(app map f t))))))
(fn print_list
(doc "Print each Int on its own line.")
(type
(fn-type
(params (con List (con Int)))
(ret (con Unit))
(effects IO)))
(params xs)
(body
(match xs
(case (pat-ctor Nil)
(lit-unit))
(case (pat-ctor Cons h t)
(seq
(do io/print_int h)
(tail-app print_list t))))))
(fn main
(doc "Map inc over [1,2,3] via polymorphic List, then print: 2,3,4.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print_list
(app map inc
(term-ctor List Cons 1
(term-ctor List Cons 2
(term-ctor List Cons 3
(term-ctor List Nil)))))))))