Files
AILang/docs/journals/2026-05-12-iter-hs.1.md
T
Brummel c56498aca7 iter hs.1: static-Str layout migration — packed-struct globals + sentinel rc-header + len
First iter of the heap-Str ABI milestone. Pure codegen refactor; every existing program produces byte-identical stdout.

Static-Str LLVM globals migrate from `[N x i8] c"…\00"` to `<{ i64, i64, [N x i8] }> <{ i64 -1, i64 N-1, [N x i8] c"…\00" }>`, carrying the UINT64_MAX sentinel rc-header (slot exploited in hs.2 via runtime short-circuit) and an explicit byte length. Two parallel intern paths (`intern_string` for raw format scaffolding, new `intern_str_literal` for language Str values) keep format strings untouched in `[N x i8]` shape.

IR-Str pointers now uniformly land on the len-field (offset +8 from rc-header, -8 from bytes). Four codegen sites that hand a Str pointer to a NUL-terminated-bytes C-API consumer now emit a +8 GEP first: `@puts` (io/print_str), `@ail_str_eq` (eq__Str intercept), `@ail_str_compare` (compare__Str intercept), and `@strcmp` (lower_eq Str arm — the 4th site was not in the plan; surfaced by Task-5 e2e regression and fixed inline).

6 new IR-shape pinning tests; `hello.ll` snapshot regenerated; cross_lang.py + compile_check.py + full `cargo test --workspace` green.
2026-05-12 16:48:24 +02:00

5.7 KiB

iter hs.1 — Heap-Str ABI: Static-Str layout migration

Date: 2026-05-12 Started from: 69bb5f9952 Status: DONE Tasks completed: 5 of 5

Summary

First iter of the heap-Str ABI milestone. Pure codegen-side refactor: language Str literals now emit as packed-struct LLVM globals <{ i64, i64, [N+1 x i8] }> <{ i64 -1, i64 N, [N+1 x i8] c"...\00" }> carrying a UINT64_MAX sentinel rc-header in the first slot, an explicit byte length in the second, and the bytes plus trailing NUL in the array tail. The IR-Str-pointer convention is now uniform: a language Str value flowing through the IR is a ptr landing on the len-field (offset +8 from the header, -8 from the bytes). Every codegen site that hands a Str pointer to a NUL-terminated-bytes C-API consumer emits a getelementptr inbounds i8, ptr <v>, i64 8 immediately before the call. The sentinel slot is hardcoded in the global initialiser and not yet observed by any caller — hs.2 will add the UINT64_MAX short-circuit to ailang_rc_inc / ailang_rc_dec in runtime/rc.c to exploit it. Format strings (%lld\n, %g\n, true\n, false\n) are unchanged — they are runtime-internal scaffolding, never flow as language Str values, and remain in the raw [N x i8] shape via the original intern_string / all_strings path. Acceptance verified end-to-end: full cargo test --workspace green (no regressions across 74 e2e fixtures), bench/compile_check.py exit 0, bench/cross_lang.py exit 0 (byte-identical stdout across all ail/c benchmark pairs).

Per-task notes

  • iter hs.1.1: introduced parallel str_literals interning table on Emitter, intern_str_literal fn, workspace-level all_str_literals aggregation, and a parallel global-emission loop emitting the packed-struct shape. Switched both Literal::Str arms (emit_const_def, lower_term) to call intern_str_literal and materialise a constexpr getelementptr inbounds (<{ i64, i64, [N x i8] }>, ptr @g, i32 0, i32 1) landing on the len-field. Two pinning tests added.
  • iter hs.1.2: io/print_str arm now emits a +8 GEP to land on the bytes pointer before calling @puts. One pinning test added.
  • iter hs.1.3: eq__Str arm of try_emit_primitive_instance_body now emits +8 GEPs on both operands before calling @ail_str_eq. One pinning test added.
  • iter hs.1.4: compare__Str arm of try_emit_primitive_instance_body now emits +8 GEPs on both operands before calling @ail_str_compare. One pinning test added.
  • iter hs.1.5: regenerated crates/ail/tests/snapshots/hello.ll via UPDATE_SNAPSHOTS=1. Full cargo test --workspace revealed a regression in eq_demo traced to a fourth codegen site not enumerated in the plan: lower_eq's "Str" arm calls @strcmp inline (separate from the eq__Str instance-method intercept) to lower == on Str literals and Str-pattern desugaring in classify_str. Fixed inline with the same +8 GEP shape; one additional pinning test added.

Concerns

  • The plan claimed three codegen sites pass a Str pointer to a NUL-terminated-bytes C-API consumer (@puts, @ail_str_eq, @ail_str_compare); in fact a fourth site exists in lower_eq's "Str" arm, calling @strcmp directly. This is the dispatch path for the surface-level == operator on Str and for (pat-lit "...") desugar against a Str scrutinee. The fourth site is required to meet the iter's stated acceptance criterion (byte-identical stdout); fixing it was discovered via the e2e regression in eq_demo rather than via the per-task IR-shape checks. The fix has the same shape as Tasks 2-4 and the corresponding pinning test (lower_eq_str_calls_strcmp_with_bytes_pointer) is added. The plan's "Files this plan creates or modifies" enumeration should list crates/ailang-codegen/src/lib.rs:2520-2529 as a fifth modify site for any future re-execution.
  • The plan's two test fixtures in Task 1 contained schema-syntax errors that prevented compilation: ConstDef.body (actual: value), Term::App { f, args } (actual: callee, args, tail), and Term::App { Term::Var "io/print_str", ... } for what is actually a Term::Do effect op. Tests were adapted to compile while preserving the substantive assertions verbatim. Same applies to the test in Task 2 (Term::AppTerm::Do for the io/print_str invocation and added effects: ["IO"] on the main fn so the typecheck-side dispatch finds the effect). These were small per-test fixture adaptations, not changes to the asserted IR shape.

Known debt

  • The sentinel value -1 (UINT64_MAX) is hardcoded in the global initialiser. hs.2 adds the matching short-circuit check in ailang_rc_inc / ailang_rc_dec so static-Str pointers can flow through generic RC paths without ref-count traffic. Until hs.2 lands, no caller actually observes the sentinel — static Str slabs are still invisible to the RC runtime by construction (the call sites for inc / dec against Str values are still gated by the same uniqueness-inference pass that elided them pre-iter).

Files touched

  • crates/ailang-codegen/src/lib.rs — Emitter field, intern fn, workspace aggregation, global emission, two Literal::Str arm switches, four C-API-callsite +8 GEP adjustments (io/print_str, eq__Str, compare__Str, lower_eq Str arm), six new IR-shape pinning tests in the test module.
  • crates/ail/tests/snapshots/hello.ll — regenerated snapshot showing the new packed-struct global and the +8 GEP before @puts. No other snapshot under crates/ail/tests/snapshots/ changed (other snapshots contain only format-string globals which kept the raw [N x i8] shape).

Stats

bench/orchestrator-stats/2026-05-12-iter-hs.1.json