# Embedding ABI ## Embedding ABI `ail build --emit=staticlib` compiles a module to a relocatable `lib.a` (program objects only) plus a separate `libailang_rt.a` (the RC runtime: `rc.c` + `str.c`), with **no** `@main` trampoline and **no** `MissingEntryMain` requirement — a kernel module is a library. Each `fn` carrying `(export "")` (schema: `FnDef.export`) is emitted as an externally-visible C entrypoint `@` forwarding to the internal `@ail__`. The symbol is author-chosen and decoupled from the `ail__` mangling so a module/fn rename does not move the C symbol. The set that can cross the boundary is exactly: `Int` (lowered `i64`), `Float` (lowered `double`), or a single-constructor record whose every field is one of those (crossing as a bare `ptr` to the box layout below); the fn's effect set must be empty. There is no general value-marshalling layer — the host hand-constructs and hand-reads that box layout directly, so the enumerated set *is* the whole contract, narrow by construction, not a subset of a wider embedding ABI. Because the box layout is the host's contract surface with no accessor indirection, it is a **one-way commitment frozen as of M3**: a compiler change MUST NOT move the box offsets below for an exported type, nor invert the host-free rule. These are enforced at `ail check` (`export-non-scalar-signature`, `export-has-effects`) — an effectful or non-scalar export *fails to typecheck*. Every exported entrypoint takes a mandatory leading `ailang_ctx_t*` (M2): a per-thread embedding context created by `ailang_ctx_new()` and released by `ailang_ctx_free()`, owned by the calling thread for its lifetime. The host links one `ailang_ctx_t` per OS worker thread and the runtime accounts RC alloc/free into it: the per-allocation hot path (the per-ctx counters and the per-object refcount header) is non-atomic by design and never shared — a box never crosses a thread (`Ctx: !Send`) and each `ailang_ctx_t` is single-thread-per-ctx. The one datum a multi-threaded host shares is the global RC-stats fallback counter (used when no ctx is bound); it is atomic-relaxed so the swarm's leak accounting is exact. The swarm artefact is data-race-free, sanitiser-verified. The staticlib swarm artefact is **RC-only**: `ail build --emit=staticlib` rejects `--alloc=bump` (the bench stub is leak-only and not swarm-safe; `--alloc=gc` no longer exists as a CLI value — see the Boehm-retirement iter). The value/record layout is **frozen as of M3** (see [Frozen value layout](0006-frozen-value-layout.md)); the ctx-threaded C signature is the M2 shape. Export parameters are written **bare**: a scalar type carries no `own`/`borrow` mode (a single-constructor record export parameter, by contrast, carries `own`/`borrow` — see [memory model](0008-memory-model.md) for the full ownership contract; the frozen value layout below specifies how that contract lands at the C ABI). The canonical M1 export shape: ``` (fn step (export "backtest_step") (type (fn-type (params (con Int) (con Int)) (ret (con Int)))) (params state sample) (body (app + state (app * sample sample)))) ``` `ail emit-ir --emit=staticlib` prints this kernel's LLVM IR (the external `@` forwarders, no `@main`) instead of the executable-path `main`-required rejection — the Decision-5 IR-readability affordance for a `main`-free kernel. Ratified by: `crates/ailang-codegen/tests/embed_record_layout_pin.rs`.