Files
AILang/docs/journals/2026-05-12-iter-24.1.md
T
Brummel f38bad8c2b iter 24.1: bool_to_str + str_clone runtime + codegen wiring
First iter of milestone 24 (Show + print rewire). Wires two new
heap-Str-producing primitives parallel to hs.4's int_to_str /
float_to_str:

- runtime/str.c gains ailang_bool_to_str(bool) → heap-Str "true" /
  "false" and ailang_str_clone(const char *) → memcpy'd heap-Str
  copy. Both use the existing str_alloc slab helper.
- builtins.rs + synth.rs install the two signatures lockstep with
  ret_mode: Own; str_clone carries param_modes: [Borrow].
- IR-header preamble gains two unconditional `declare ptr @...`
  lines; Emitter::lower_app gets two new arms; is_static_callee
  whitelist extends with the two names.
- Five IR snapshots regenerate for the two new declares.
- Pre-existing-drift fix: int_to_str row added to builtins.rs::list()
  (hs.4 installed env.globals entry but missed the list() row).

Substantive deviation flagged by orchestrator (DONE_WITH_CONCERNS):
builtin signatures registered in uniqueness.rs::infer_module and
linearity.rs::check_module_with_visible (8 LOC × 2 files), symmetric
to iter 23.4-prep's class-method registration in the same globals
maps. Without this fix str_clone's param_modes: [Borrow] is invisible
to the App-arg walker, src_heap walks as Position::Consume, the
scope-close ailang_rc_dec is gated off, and the
str_clone_cross_realisation_uniform_abi test's plan-literal
`frees == 3` assertion does not hold. The fix is the substantively
correct repair, not a design departure.

9 new tests: 2 builtins-install unit, 2 IR-shape unit pins, 5 E2E
(2 RC-stats, 2 stdout-smoke for both Bool branches, 1 cross-
realisation). 4 new .ail.json fixtures.

Full cargo test --workspace: 513 passed, 0 failed.
bench/compile_check.py: 24/24 stable. bench/cross_lang.py: 25/25
stable.
2026-05-12 23:47:13 +02:00

217 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# iter 24.1 — `bool_to_str` + `str_clone` runtime + codegen wiring
**Date:** 2026-05-12
**Started from:** 8bfa09adc7774a1c94af609fc4c78a70ba77be48
**Status:** DONE_WITH_CONCERNS
**Tasks completed:** 6 of 6
## Summary
First iter of milestone 24 ("Show + print rewire"). Wires two new
heap-Str-producing primitives — `bool_to_str : (Bool) -> Str` and
`str_clone : (Str borrow) -> Str` — through the runtime C code, type
checker, codegen IR lowering, and IR-header preamble, mechanically
mirror-imaging the hs.4 wiring for `int_to_str` / `float_to_str`. The
two new symbols install lockstep in `builtins.rs::install` and
`synth.rs::builtin_value_type` with `ret_mode: Own`; codegen gains
two new IR-header `declare` lines, two `Emitter::lower_app` arms, and
two `is_static_callee` whitelist entries. Plan's pre-existing-drift
fix landed alongside: `int_to_str` row added to `builtins.rs::list()`
(hs.4 added the `env.globals` entry but missed the `list()` row).
Nine new tests landed per plan: 2 builtins-install unit tests
(`install_bool_to_str_signature`, `install_str_clone_signature`),
2 IR-shape unit pins (`bool_to_str_emits_call_to_ailang_bool_to_str`,
`str_clone_emits_call_to_ailang_str_clone`), 5 E2E tests
(`bool_to_str_drop_balances_rc_stats`, `bool_to_str_emits_true_branch`,
`bool_to_str_emits_false_branch`, `str_clone_drop_balances_rc_stats`,
`str_clone_cross_realisation_uniform_abi`). Four `.ail.json` fixtures
shipped. Five IR snapshots regenerated for the two new declare lines.
One substantive deviation from the plan's "purely-additive wiring"
framing surfaced during Task 6 fixture validation: the
`str_clone_cross_realisation_uniform_abi` test's literal assertion
`allocs == 3, frees == 3, live == 0` could not hold under the
pre-24.1 uniqueness analyser because the analyser's `globals` map
does not contain builtin signatures — `str_clone`'s `param_modes:
[Borrow]` is invisible to `callee_arg_modes`, so a `src_heap` let-
binder consumed by `str_clone(src_heap)` walks as `Position::Consume`,
incrementing `consume_count` and gating off the scope-close
`ailang_rc_dec`. Fixed inline by registering builtin signatures in
both `uniqueness.rs::infer_module` and
`linearity.rs::check_module_with_visible` (8 lines × 2 files,
symmetric to the existing class-method registration the linearity
pass added in iter 23.4-prep). The fix is substantively correct (it
applies a pre-existing pattern to a new class of callees) but is
NOT in the plan's literal Edit text — see Concerns for the
discussion.
Acceptance verified: full `cargo test --workspace` green (513 tests
passing, 0 failures); the five new E2E tests pass with the plan's
literal numeric assertions; the two new IR-shape pins pass; the five
IR snapshot tests pass after `UPDATE_SNAPSHOTS=1` regen (diff is
exactly the two new `declare ptr @ailang_bool_to_str(i1)` and
`declare ptr @ailang_str_clone(ptr)` lines). `bench/compile_check.py`
24/24 stable, 0 regressions. `bench/cross_lang.py` 25/25 stable, 0
regressions. Grep verification: 44 hits across runtime/str.c +
builtins.rs + synth.rs + lib.rs (well above the plan's ≥ 10
threshold), 10 hits in snapshots (2 declares × 5 files), 0 build
warnings.
## Per-task notes
- iter 24.1.1: Appended `ailang_bool_to_str(bool b) -> char *` and
`ailang_str_clone(const char *src) -> char *` to `runtime/str.c`
after `ailang_float_to_str` (str.c:128-143). Both use the existing
static `str_alloc(uint64_t)` helper for heap-Str slab allocation;
`bool_to_str` writes either "true" (4 bytes) or "false" (5 bytes);
`str_clone` reads `len` from offset 0 of source and memcpy's bytes
+ trailing NUL to a fresh slab. Standalone clang -c compile green.
- iter 24.1.2: Installed `bool_to_str : (Bool) -> Str` and
`str_clone : (Str borrow) -> Str` in `builtins.rs::install`
(builtins.rs:213-232, between the existing `int_to_str` block and
the `is_nan` block), both with `ret_mode: ParamMode::Own`. Mirror-
installed the same two signatures in `synth.rs::builtin_value_type`
(synth.rs:181-194, lockstep). Added three rows to
`builtins.rs::list()` (`int_to_str` drift fix from hs.4 +
`bool_to_str` + `str_clone`). Two new unit tests
(`install_bool_to_str_signature`, `install_str_clone_signature`)
appended to `builtins.rs::tests` using the fallback explicit
`Term::Lit` shape (plan-noted: `lit_bool` / `lit_str` helpers do
not exist; only `lit_int` / `lit_float` are defined). Both new
unit tests PASS.
- iter 24.1.3: Codegen IR wiring. (a) Extended the IR-header preamble
at `lib.rs:539-548` with two unconditional declares
`declare ptr @ailang_bool_to_str(i1)` and
`declare ptr @ailang_str_clone(ptr)`. (b) Inserted two new arms in
`Emitter::lower_app` at `lib.rs:1934-1969` (after the existing
`float_to_str` arm), each emitting a direct call to the runtime
C glue with arity check + `lower_term`+`fresh_ssa`+call triple
matching `int_to_str`'s structural shape. (c) Extended the
`is_static_callee` builtin-callee whitelist at `lib.rs:2133-2135`
with `bool_to_str` and `str_clone` (strictly required so
`Term::Var { name: "bool_to_str" }` reaches `lower_app`'s chain
instead of the UnknownVar fallback). Full workspace build clean.
- iter 24.1.4: Two IR-shape unit pin tests
(`bool_to_str_emits_call_to_ailang_bool_to_str`,
`str_clone_emits_call_to_ailang_str_clone`) appended at the bottom
of `lib.rs::mod tests`, mirroring the
`int_to_str_lowers_to_ailang_int_to_str_call` template. Both PASS.
- iter 24.1.5: Five IR snapshots (`hello.ll`, `list.ll`, `max3.ll`,
`sum.ll`, `ws_main.ll`) regenerated via `UPDATE_SNAPSHOTS=1`. RED
observed first as expected (5 ir_snapshot_* tests failed with
"run UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_ to refresh");
GREEN after regen. Diff is exactly the two new declare lines.
Each .ll file shows 2 matches for the new symbols.
- iter 24.1.6: Four `.ail.json` fixtures created
(`bool_to_str_drop_rc.ail.json`, `bool_to_str_smoke_false.ail.json`,
`str_clone_drop_rc.ail.json`, `str_clone_cross_realisation.ail.json`)
per the plan's verbatim JSON. Five new E2E tests appended to
`crates/ail/tests/e2e.rs` after `heap_str_repeated_print_balances_rc_stats`.
The `str_clone_cross_realisation_uniform_abi` test initially
FAILED with `frees=2, expected=3`; root-caused to the
uniqueness-analyser-globals scope gap (see Concerns); fixed by
registering builtin signatures in `uniqueness.rs::infer_module`
and `linearity.rs::check_module_with_visible`. After the fix all
five new E2E tests PASS with the plan's literal numeric
assertions. Full `cargo test --workspace`: 513 passed, 0 failed.
`bench/compile_check.py` 24/24 stable. `bench/cross_lang.py` 25/25
stable. Grep verification per plan Step 9 met: 44 code-side hits,
10 snapshot hits, 0 build warnings.
## Concerns
- **Analyser-globals registration extends the plan's "purely-additive"
scope.** The plan framed 24.1 as "Pure-additive wiring across four
crates / files." The actual landed change extends to two more
files (`uniqueness.rs`, `linearity.rs`) with 8 lines each — a fix
to the uniqueness / linearity analyser-globals scope so the
`str_clone` builtin's `param_modes: [Borrow]` is visible to the
`callee_arg_modes` walker. Without the fix, the
`str_clone_cross_realisation_uniform_abi` test's plan-literal
assertion `frees == 3` does not hold (one heap-Str slab leaks
because the App-arg walker can't see the Borrow mode → walks
`src_heap` as Consume → `consume_count == 1` → drop emission
gated off).
The fix is **symmetric** to the iter 23.4-prep extension that
registered class-method signatures in the same `globals` maps for
the same reason (a builtin/class-method callee carrying
`param_modes` would not be visible to the analyser, false-firing
`consume-while-borrowed` lints AND, in this iter, leaking heap-Str
slabs). The plan's verbatim Edit text did not include the fix; the
plan's verbatim test assertion required it. Per the orchestrator-
agent's "make the reasonable call and continue" mandate I applied
the fix in-place; it is the substantively-correct repair, not a
design departure.
The hs.4 journal's "Known debt" item on this is partially closed
by this iter (the App-side of the Borrow-walker visibility is now
fixed for builtin callees). The eob.1 fix (effect-op args walk as
Borrow regardless of callee `param_modes`) covered the `Term::Do`
side; the corresponding `Term::App` side is now covered for
builtins. User-fn `Term::App` callees still benefit from their
own def being registered in the analyser's `globals` from the
module's own def list (lines 113-115 in uniqueness.rs); imported
user-fn callees benefit from the iter 23.5 visible-extra
registration in linearity.rs (lines 220-238). So the only
remaining unknown-callee case is one this iter does not encounter.
## Known debt
- **`str_clone` could rc_inc on heap-Str input** instead of allocating
a fresh copy, but this requires distinguishing heap-Str from
static-Str at runtime — a tagging scheme the codebase deliberately
dropped post-hs.2 (UINT64_MAX sentinel removal). Out of scope per
the parent spec's "`str_clone` as rc_header bump" entry.
- **`bool_to_str` as schema-`if`** with two static-Str literals would
avoid the per-call heap allocation but requires phi-of-static-Str
through the drop-elision pipeline that is not load-bearing-ratified
today. Open commitment per the parent spec.
## Files touched
- Modified (12 files):
- `runtime/str.c` — appended `ailang_bool_to_str` (16 LOC incl.
comment) and `ailang_str_clone` (18 LOC incl. comment)
- `crates/ailang-check/src/builtins.rs` — two new
`env.globals.insert` blocks (`bool_to_str` + `str_clone`), three
new rows in `list()` (drift fix + two new), two new unit tests
- `crates/ailang-codegen/src/synth.rs` — two new arms in
`builtin_value_type` (lockstep partner of the checker insert)
- `crates/ailang-codegen/src/lib.rs` — IR-header preamble extends
with 2 new `declare` lines; 2 new `lower_app` arms; 2-entry
`is_static_callee` whitelist extension; 2 new IR-shape pin tests
- `crates/ailang-check/src/uniqueness.rs` — register builtin
signatures in `infer_module`'s `globals` map so `callee_arg_modes`
resolves builtin `param_modes` correctly
- `crates/ailang-check/src/linearity.rs` — register builtin
signatures in `check_module_with_visible`'s `globals` map for the
same reason
- `crates/ail/tests/e2e.rs` — 5 new E2E tests appended
- `crates/ail/tests/snapshots/hello.ll`,
`crates/ail/tests/snapshots/list.ll`,
`crates/ail/tests/snapshots/max3.ll`,
`crates/ail/tests/snapshots/sum.ll`,
`crates/ail/tests/snapshots/ws_main.ll` — golden-IR regen via
`UPDATE_SNAPSHOTS=1`; diff is exactly the two new
`declare ptr @ailang_bool_to_str(i1)` and
`declare ptr @ailang_str_clone(ptr)` lines
- Created (4 files):
- `examples/bool_to_str_drop_rc.ail.json` — RC-stats fixture for
`bool_to_str true` (4-byte slab, single drop)
- `examples/bool_to_str_smoke_false.ail.json` — stdout-smoke
fixture for `bool_to_str false` (5-byte slab)
- `examples/str_clone_drop_rc.ail.json` — RC-stats fixture for
`str_clone "hello"` (static-Str input → fresh heap-Str clone)
- `examples/str_clone_cross_realisation.ail.json` — cross-
realisation invariant: clones one heap-Str (output of
`int_to_str 42`) AND one static-Str (literal `"abc"`), printing
both; pins the "uniform consumer ABI" claim
## Stats
bench/orchestrator-stats/2026-05-12-iter-24.1.json