roadmap: Embedding ABI arc — M1–M5 (P1) + deferred array primitive (P2)

The data-server-driven goal: a compiled AILang kernel callable
in-process from a concurrent Rust host so a backtest swarm runs
each backtest as an independent single-threaded computation.
Decomposed into five chronologically-bounded milestones (M1
linkable artifact+scalar entrypoint, M2 per-thread ctx+concurrency
safety, M3 frozen value layout+ADT crossing, M4 List-ADT sequence
crossing, M5 ail-embed adapter+swarm fieldtest), each its own full
brainstorm->plan->implement->audit cycle. Shared motivation + three
audited invariants (clean core / single-threaded per computation /
native-AOT-only) carried on the M1 entry. The flat array/slice
primitive is recorded P2 as a measured performance follow-up
gated on M5's fieldtest, not a capability gap — keeping the ABI
arc's scope clean. Decision made in the 2026-05-18 data-server
analysis chat; no spec yet (brainstorm produces M1's spec at start).
This commit is contained in:
2026-05-18 12:51:51 +02:00
parent 3fb184417d
commit 064599e091
+130
View File
@@ -129,6 +129,119 @@ work progresses.
## P1 — Next
- [ ] **\[milestone\]** Embedding ABI — M1: linkable artifact +
scalar C entrypoint + `main`-free lifecycle. First of a
five-milestone arc (M1M5) that makes a compiled AILang kernel
callable in-process from a concurrent Rust host, so a swarm of
backtests over `data-server`-supplied market data runs each
backtest as an independent single-threaded AILang computation.
**Motivation (shared by M1M5).** The target deployment of
AILang is financial-data analysis: a host (Rust, owning
`data-server`'s shared zero-copy cache) drives a swarm of
backtests; each backtest is the pure per-chunk fold AILang is
built for (`(State, Chunk) -> State`, the `sum_sq` loop/recur
shape). Today AILang is a whole-program compiler whose only
output channel is `io/print_str` — there is no way to invoke a
compiled AILang function from a host with structured arguments
and a typed return. That missing call surface, not the math, is
the entire blocker. This is a backend/packaging capability, not
a language feature: the language semantics do not change. Its
justification is strategic (this is where the language is meant
to run), not the standard authoring-utility gate — a deliberate,
user-made direction call (2026-05-18 chat).
**Invariants (audited at every close in this arc).**
(1) *Clean core*`ailang-core` / `ailang-codegen` / the
runtime gain no `data-server`/finance knowledge and no
dependency on either; the only place AILang and `data-server`
meet is a separate host-side adapter crate (`ail-embed`, M5)
that depends on both while neither depends on it. The `audit`
architect review verifies this each close.
(2) *Single-threaded per computation* — all concurrency is
structural and owned by the host; no AILang value ever crosses
a thread boundary, so non-atomic RC and "no shared mutable
refs" stay intact.
(3) *Native-AOT only* — no interpreter/playground is introduced
(the "emit LLVM → clang -O2" performance pillar is load-bearing
for the swarm).
**M1 scope.** CLI emit-mode producing a relocatable archive
instead of linking an executable; codegen exports an
author-designated `fn` under a stable C symbol with a
C-compatible signature instead of wrapping `main` (today
hard-coded `define i32 @main() { call i8 @ail_<mod>_main() }`,
`crates/ailang-codegen/src/lib.rs:556`); a minimal explicit
lifecycle so the runtime is usable with no `main`-time setup.
Scalar signatures only (`(Int)->Int`, `(Float,Float)->Float`)
— no ADT crosses the boundary yet. Coherent stop: AILang
arithmetic callable from C/Rust, proven by a host harness that
links the `.a`, calls the symbol, asserts the return.
- context: 2026-05-18 chat (`data-server` analysis →
embedding-ABI decomposition). No spec yet; brainstorm
produces `docs/specs/<date>-embedding-abi-m1.md` at M1 start.
- [ ] **\[milestone\]** Embedding ABI — M2: per-thread runtime
context + concurrency safety. `ailang_ctx_new()/_free` threaded
through every call; neutralise the two process-global hazards
the runtime read surfaced — the non-atomic
`g_rc_alloc_count` / `g_rc_free_count` counters + `atexit`
stats hook (`runtime/rc.c:8698`), and the allocator backing
(toward pure per-thread RC, no shared Boehm collector; aligns
with the standing P2 "Boehm full retirement" todo). Still
scalar-only so any failure is attributable to concurrency, not
marshalling. IO-at-the-boundary (`io/print_str` via a
per-instance sink) explicitly deferred here: pure
`(State,Chunk)->State` kernels have no effects; revisit only
if a logging kernel appears — deliberately not a separate
roadmap entry, by feature-acceptance discipline. Coherent stop:
swarm-safe scalar kernels, tsan-clean under N threads.
- depends on: Embedding ABI — M1.
- context: 2026-05-18 chat; runtime hazard read
(`runtime/rc.c` global stats + `atexit`).
- [ ] **\[milestone\]** Embedding ABI — M3: frozen value layout +
single ADT/record crossing + RC ownership contract. Host
constructs a single-ctor record (Int/Float fields), passes it,
the kernel returns a record the host reads and frees; the RC
ownership-transfer contract at the call boundary is specified
and the `rc_header`/ADT-box layout documented as a *frozen*
ABI. Sequenced after M2 — a one-way stability commitment, so
the frozen ABI must already include the ctx parameter shape
(substantive ordering reason, not effort). Covers the `State`
value crossing in/out. Coherent stop: a record in/out with
alloc==free across many calls.
- depends on: Embedding ABI — M2.
- context: 2026-05-18 chat. `runtime/rc.c`'s header already
anticipates this ("The ABI defined here is stable … unless
the layout itself shifts").
- [ ] **\[milestone\]** Embedding ABI — M4: sequence crossing via
the existing `List` ADT. Host builds a `List Record` per chunk;
`(State, List Record) -> State` callable end-to-end. Pure
ABI/marshalling, no language change — applies M3's frozen
layout repeatedly. Makes the real fold runnable over real
chunks. The array-vs-cons-list performance question (1024 RC
cons-cells per chunk on the hot path) is deliberately *not* in
scope here — capability now, performance as a separate later
non-ABI milestone (P2 below), keeping the user's "focus on the
ABI, ignore the array gap" cut. Coherent stop:
`(State, List M1) -> State` runs.
- depends on: Embedding ABI — M3.
- context: 2026-05-18 chat.
- [ ] **\[milestone\]** Embedding ABI — M5: `ail-embed` adapter +
`data-server` wiring + thread-swarm backtest (fieldtest
target). The host-side adapter crate (sole meeting point of
`data-server` and AILang, per Invariant 1) wires
`data-server`'s `while let next_chunk` loop to the AILang
kernel via the ABI; an actual small backtest runs over a
thread swarm. Natural `fieldtest` milestone: existence proof
of the whole goal + friction harvest feeding the
array-primitive decision. Coherent stop: the goal itself.
- depends on: Embedding ABI — M4.
- context: 2026-05-18 chat.
- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
malloc-backed, refcounted `Str` values alongside the existing
static `@.str_*` globals. Today the `Str` path is static-only
@@ -165,6 +278,23 @@ work progresses.
## P2 — Medium-term
- [ ] **\[milestone\]** Flat array/slice primitive — performance
follow-up to the Embedding ABI arc, *not* a capability gap.
AILang's only sequence is the recursive `List` ADT (one RC cell
per element); marshalling a 1024-record chunk across the
embedding boundary builds a 1024-cell cons-chain per chunk on a
multi-GB hot path (Embedding ABI M4 ships the capability this
way). A contiguous array/slice primitive would remove that, but
it is a *language* change (interacts with uniqueness inference,
RC, and the structurally-decreasing-recursion totality story)
and is explicitly out of the ABI arc's scope. Sequenced after
M5 so the decision is driven by M5's fieldtest measurements,
not speculation.
- depends on: Embedding ABI — M5 (for the measured
justification).
- context: 2026-05-18 chat (user deferred this explicitly to
keep the ABI arc clean).
- [ ] **\[milestone\]** Iteration-totality story — structural +
Int-bounded total recursion with *enforced* non-negativity.
AILang's iteration story stays as-is (structural / tail recursion;