From cc2d6944c11eb7d6f4e45f063bb6c2d690ad3106 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 21:49:45 +0200 Subject: [PATCH] iter 23.2.1: runtime/str.c with ail_str_eq + unconditional link --- crates/ail/src/main.rs | 65 ++++++++++++++++++++++++++++++++++++++++++ runtime/str.c | 26 +++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 runtime/str.c diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 5723fed..c7b6bb8 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -1970,6 +1970,37 @@ fn locate_rc_runtime() -> Result { ) } +/// Locate the workspace-root `runtime/str.c` file relative to the +/// `ail` binary, mirroring [`locate_rc_runtime`]. Iter 23.2: +/// `runtime/str.c` houses Str-runtime primitives (`ail_str_eq`, +/// later `ail_str_compare`) and is linked unconditionally for every +/// alloc strategy — `@ail_str_eq` is declared in the emitted IR +/// header (codegen) without an alloc-strategy guard, so the symbol +/// must always be resolvable at link time. +fn locate_str_runtime() -> Result { + let candidates = [ + std::env::current_exe().ok(), + std::env::current_dir().ok(), + ]; + for start in candidates.iter().flatten() { + let mut cur: &Path = start.as_path(); + loop { + let candidate = cur.join("runtime").join("str.c"); + if candidate.exists() { + return Ok(candidate); + } + match cur.parent() { + Some(p) => cur = p, + None => break, + } + } + } + anyhow::bail!( + "could not locate `runtime/str.c` (linked unconditionally). \ + Run `ail` from inside the AILang workspace." + ) +} + /// Iter 9b: shared build helper for `Cmd::Build` and `Cmd::Run`. /// Loads the workspace, runs the typechecker, emits IR, and links via /// clang. On typecheck failure, prints diagnostics to stderr and exits @@ -2060,6 +2091,29 @@ fn build_to( let out_bin = out.unwrap_or_else(|| Path::new(".").join(&ws.entry).with_extension("")); let mut clang = std::process::Command::new("clang"); clang.arg(opt).arg("-o").arg(&out_bin).arg(&ll_path); + // Iter 23.2: compile and link `runtime/str.c` unconditionally — + // `@ail_str_eq` is emitted in the IR header without an alloc-strategy + // guard (it backs the prelude `eq__Str` mono symbol). The .o is + // cached at /str.o per build invocation; if a program never + // monomorphises `eq__Str`, clang -O2 dead-strips the symbol so the + // linker overhead is fixed and negligible. + let str_src = locate_str_runtime()?; + let str_obj = tmpdir.join("str.o"); + let cstatus = std::process::Command::new("clang") + .arg("-O2") + .arg("-c") + .arg(&str_src) + .arg("-o") + .arg(&str_obj) + .status() + .context("compiling runtime/str.c")?; + if !cstatus.success() { + anyhow::bail!( + "clang failed compiling str.c (status {})", + cstatus + ); + } + clang.arg(&str_obj); match alloc { ailang_codegen::AllocStrategy::Gc => { // Boehm conservative GC (Decision 9 / Iter 14f). The lowered @@ -2139,6 +2193,17 @@ fn build_to( mod tests { use super::compose_merge_prose_prompt; + #[test] + fn locate_str_runtime_finds_workspace_str_c() { + let p = super::locate_str_runtime().expect("locate runtime/str.c"); + assert!( + p.ends_with("runtime/str.c"), + "expected path ending in runtime/str.c, got {}", + p.display() + ); + assert!(p.exists(), "located path must exist on disk: {}", p.display()); + } + /// The composed prompt must contain all three payloads /// byte-for-byte — the LLM has to see exactly what the user wrote, /// the original module's Form-A bytes, and the embedded spec, with diff --git a/runtime/str.c b/runtime/str.c new file mode 100644 index 0000000..d238d4c --- /dev/null +++ b/runtime/str.c @@ -0,0 +1,26 @@ +/* AILang Str runtime primitives. + * + * Provides ABI-stable string operations against the existing + * constant-Str layout (NUL-terminated byte buffer, passed as + * `ptr` in LLVM IR). Lives separately from runtime/rc.c so that + * a future heap-Str ABI milestone can swap the implementations + * without touching the RC runtime. + * + * Linked unconditionally by `ail build` (Gc, Bump, Rc) so that + * any program that monomorphises `eq__Str` (auto-loaded from + * the prelude) finds the symbol. Programs that never call + * `eq` on Str leave @ail_str_eq as a dead-stripped no-op at + * link time under clang -O2. + */ + +#include +#include + +/* Byte-equality on NUL-terminated strings. Mirrors strcmp(...) == 0 + * but exposed under a stable AILang-namespaced ABI so the codegen + * caller does not assume libc's strcmp shape. Heap-Str ABI milestone + * will replace the body with length-prefixed comparison without + * changing the signature. */ +bool ail_str_eq(const char *a, const char *b) { + return strcmp(a, b) == 0; +}