fbeeadeba5
Completes Embedding ABI — M2.1 (Tasks 1-4 committed c9a84b3 as the
known-good subset; this commit lands Tasks 5-9 from the re-dispatch
against the Boss-corrected plan).
- T5: swarm.c per-thread-ctx scalar-swarm capability demo (no
SHARED_CTX) + rc_accounting_shared_ctx_is_flagged_by_tsan added to
the item-1 harness driver (de-globalisation negative-control teeth,
where a shared ctx genuinely races on ctx->alloc_count).
- T6: M1 host.c migrated to the ctx ABI; embed_e2e green (s==25
through the ctx-threaded forwarder).
- T7: DESIGN.md current-state update (provisional-until-M3 narrowed
to the value/record layout; Decision-10 per-thread-ctx note);
docs_honesty-pinned sentence + (con Int) snippet preserved verbatim.
- T8: regression gate — workspace green; null-ctx fallback
byte-behaviour preserved.
- T9: bench-trio — compile_check/cross_lang clean; check.py
tracked-P2 noise only, causally exonerated (zero codegen/runtime
diff vs HEAD), NO baseline ratified.
Boss-verified directly (not on report trust): docs_honesty_pin 5/0,
design_schema_drift 8/0, embed_export_hash_stable 1/0,
embed_export_gate 6/0, embed_e2e 1/0, embed_staticlib_alloc_guard
2/0, print_no_leak_pin 1/0 + e2e rc_stats 4/0; the tsan coherent-stop
proven by hand — per-ctx clean (exit 0, 0 warnings), shared-ctx a
real ThreadSanitizer data race at rc.c:157/:208 (exit 66).
Known debt (journal + roadmap): DESIGN.md '## Embedding ABI (M1)'
header still says (M1) while the body mirrors M2 — out of Task 7's
scope-limited regions by design; post-audit doc-honesty tidy.
Milestone-close audit next.
79 lines
3.7 KiB
Rust
79 lines
3.7 KiB
Rust
//! M2 item 1: direct rc-accounting de-globalisation proof. Compiles
|
|
//! runtime/rc.c + runtime/str.c into libailang_rt.a, links the
|
|
//! rc_accounting_tsan.c harness against it under -fsanitize=thread.
|
|
//! Per-thread-ctx build: tsan-clean + exit 0. RED until rc.c gains
|
|
//! ailang_ctx_* + __ail_tls_ctx (link error: undefined symbols).
|
|
use std::process::Command;
|
|
use std::path::PathBuf;
|
|
|
|
fn ws_root() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
|
}
|
|
|
|
fn cc(args: &[&str], ctx: &str) {
|
|
let st = Command::new("clang").args(args).status().expect("spawn clang");
|
|
assert!(st.success(), "clang failed: {ctx}");
|
|
}
|
|
|
|
#[test]
|
|
fn rc_accounting_per_ctx_is_tsan_clean() {
|
|
let root = ws_root();
|
|
let tmp = std::env::temp_dir().join("ail_m2_rcacct");
|
|
std::fs::create_dir_all(&tmp).unwrap();
|
|
let rc_o = tmp.join("rc.o");
|
|
let str_o = tmp.join("str.o");
|
|
let rt_a = tmp.join("libailang_rt.a");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-c", "-o", rc_o.to_str().unwrap(),
|
|
root.join("runtime/rc.c").to_str().unwrap()], "rc.c");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-c", "-o", str_o.to_str().unwrap(),
|
|
root.join("runtime/str.c").to_str().unwrap()], "str.c");
|
|
let st = Command::new("ar").args(["rcs", rt_a.to_str().unwrap(),
|
|
rc_o.to_str().unwrap(), str_o.to_str().unwrap()]).status().unwrap();
|
|
assert!(st.success(), "ar failed");
|
|
let bin = tmp.join("rcacct");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-pthread",
|
|
"-o", bin.to_str().unwrap(),
|
|
root.join("crates/ail/tests/embed/rc_accounting_tsan.c").to_str().unwrap(),
|
|
rt_a.to_str().unwrap()], "link rc_accounting_tsan");
|
|
let out = Command::new(&bin).env("TSAN_OPTIONS", "halt_on_error=1")
|
|
.output().expect("run harness");
|
|
assert!(out.status.success(),
|
|
"per-ctx rc-accounting harness must exit 0 tsan-clean; stderr:\n{}",
|
|
String::from_utf8_lossy(&out.stderr));
|
|
}
|
|
|
|
#[test]
|
|
fn rc_accounting_shared_ctx_is_flagged_by_tsan() {
|
|
// Negative control: all N threads share one ailang_ctx_t* while
|
|
// calling ailang_rc_alloc/ailang_rc_dec → concurrent
|
|
// ctx->alloc_count++ is a genuine data race tsan MUST report.
|
|
// Proves the de-globalisation property is not vacuous.
|
|
let root = ws_root();
|
|
let tmp = std::env::temp_dir().join("ail_m2_rcacct_neg");
|
|
std::fs::create_dir_all(&tmp).unwrap();
|
|
let rc_o = tmp.join("rc.o");
|
|
let str_o = tmp.join("str.o");
|
|
let rt_a = tmp.join("libailang_rt.a");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-c", "-o", rc_o.to_str().unwrap(),
|
|
root.join("runtime/rc.c").to_str().unwrap()], "rc.c");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-c", "-o", str_o.to_str().unwrap(),
|
|
root.join("runtime/str.c").to_str().unwrap()], "str.c");
|
|
let st = Command::new("ar").args(["rcs", rt_a.to_str().unwrap(),
|
|
rc_o.to_str().unwrap(), str_o.to_str().unwrap()]).status().unwrap();
|
|
assert!(st.success(), "ar failed");
|
|
let bin = tmp.join("rcacct_neg");
|
|
cc(&["-O1", "-g", "-fsanitize=thread", "-pthread", "-DSHARED_CTX",
|
|
"-o", bin.to_str().unwrap(),
|
|
root.join("crates/ail/tests/embed/rc_accounting_tsan.c").to_str().unwrap(),
|
|
rt_a.to_str().unwrap()], "link rc_accounting_tsan -DSHARED_CTX");
|
|
let out = Command::new(&bin).env("TSAN_OPTIONS", "halt_on_error=1")
|
|
.output().expect("run rcacct neg");
|
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
assert!(
|
|
!out.status.success() || stderr.contains("WARNING: ThreadSanitizer"),
|
|
"shared-ctx rc-accounting negative control MUST be tsan-flagged \
|
|
(the de-globalisation teeth); exit={:?} stderr:\n{stderr}",
|
|
out.status.code()
|
|
);
|
|
}
|