Iter 18b: RC runtime + --alloc=rc routing
Wires up reference-counting allocator end-to-end without any inc/dec emission. Programs run under --alloc=rc and produce correct stdout (validated against --alloc=gc); they leak every allocation, exactly like the pre-Boehm era. The point of 18b is to establish the runtime contract before 18c adds the inc/dec-emission codegen pass. runtime/rc.c — new file. 8-byte uint64 refcount header prepended to every payload; ailang_rc_alloc(size) returns a ptr to the payload (header at ptr-8). ailang_rc_inc / dec are declared but never called by codegen yet; they exist so 18c can wire codegen against a stable runtime ABI. dec frees on zero refcount but does NOT recursively dec child references — that's 18c's job once it has per-ctor type info. crates/ailang-codegen/src/lib.rs — AllocStrategy::Rc variant added; fn_name() returns "ailang_rc_alloc". Single-line extension because Iter 18a's bump path had already centralised the allocator-symbol decision on fn_name() for all four allocation sites. crates/ail/src/main.rs — --alloc=rc accepted by Build/Run; parse_alloc_strategy extended; locate_rc_runtime() helper mirrors locate_bump_runtime; new Rc arm in build_to compiles runtime/rc.c with clang -O2 -c and links the resulting .o into the final binary (no -lgc). E2E coverage: alloc_rc_produces_same_stdout_as_gc on list.ail.json (42), alloc_rc_matches_gc_on_std_list_demo for broader allocation-site coverage. Total e2e bundle: 51 tests (was 49). Hand-verified on: sum.ail.json --alloc=rc → 55 list.ail.json --alloc=rc → 42 borrow_own_demo.ail.json --alloc=rc → 3 then 6 std_list_demo.ail.json --alloc=rc → matches --alloc=gc cargo build/test --workspace green; git diff examples/ empty.
This commit is contained in:
@@ -37,6 +37,45 @@ fn build_and_run(example: &str) -> String {
|
||||
String::from_utf8(output.stdout).expect("stdout utf8")
|
||||
}
|
||||
|
||||
/// Iter 18b: build with an explicit `--alloc=<alloc>` and run.
|
||||
/// Mirrors [`build_and_run`] but threads the allocator selector through
|
||||
/// to `ail build`. Used by the alloc-equivalence tests that assert the
|
||||
/// `gc` and `rc` paths produce byte-identical stdout.
|
||||
fn build_and_run_with_alloc(example: &str, alloc: &str) -> String {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
|
||||
let src = workspace.join("examples").join(example);
|
||||
let tmp = std::env::temp_dir().join(format!(
|
||||
"ailang_e2e_alloc_{}_{}_{}",
|
||||
alloc,
|
||||
example.replace('.', "_"),
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&tmp).unwrap();
|
||||
let out = tmp.join("bin");
|
||||
let status = Command::new(ail_bin())
|
||||
.args([
|
||||
"build",
|
||||
src.to_str().unwrap(),
|
||||
&format!("--alloc={alloc}"),
|
||||
"-o",
|
||||
])
|
||||
.arg(&out)
|
||||
.status()
|
||||
.expect("ail build failed to run");
|
||||
assert!(
|
||||
status.success(),
|
||||
"ail build --alloc={alloc} failed for {example}"
|
||||
);
|
||||
let output = Command::new(&out).output().expect("execute binary");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"binary {} (--alloc={alloc}) exited non-zero",
|
||||
out.display()
|
||||
);
|
||||
String::from_utf8(output.stdout).expect("stdout utf8")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sum_1_to_10_is_55() {
|
||||
let stdout = build_and_run("sum.ail.json");
|
||||
@@ -1265,3 +1304,32 @@ fn eq_demo() {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 18b: --alloc=rc routes allocation through ailang_rc_alloc
|
||||
/// (8-byte refcount header, libc malloc backing). With no inc/dec
|
||||
/// emission yet (18c work), programs leak under this mode but must
|
||||
/// still produce correct stdout — that's the validation 18b ships.
|
||||
#[test]
|
||||
fn alloc_rc_produces_same_stdout_as_gc() {
|
||||
// Pick a fixture with non-trivial allocation: list-building + match.
|
||||
let example = "list.ail.json";
|
||||
let stdout_gc = build_and_run_with_alloc(example, "gc");
|
||||
let stdout_rc = build_and_run_with_alloc(example, "rc");
|
||||
assert_eq!(stdout_gc, stdout_rc, "alloc=rc must match alloc=gc");
|
||||
assert_eq!(stdout_rc.trim(), "42");
|
||||
}
|
||||
|
||||
/// Iter 18b: extends `alloc_rc_produces_same_stdout_as_gc` to a larger
|
||||
/// fixture (`std_list_demo`) so more allocation sites — folds, maps,
|
||||
/// cross-module ctors — are exercised under `--alloc=rc`. Same
|
||||
/// invariant: stdout must be byte-identical to the `gc` build.
|
||||
#[test]
|
||||
fn alloc_rc_matches_gc_on_std_list_demo() {
|
||||
let example = "std_list_demo.ail.json";
|
||||
let stdout_gc = build_and_run_with_alloc(example, "gc");
|
||||
let stdout_rc = build_and_run_with_alloc(example, "rc");
|
||||
assert_eq!(
|
||||
stdout_gc, stdout_rc,
|
||||
"alloc=rc must match alloc=gc on std_list_demo"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user