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.
This commit is contained in:
@@ -536,6 +536,16 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
// clang -O2 dead-strips the declarations when no caller exists.
|
||||
out.push_str("declare ptr @ailang_int_to_str(i64)\n");
|
||||
out.push_str("declare ptr @ailang_float_to_str(double)\n");
|
||||
// Iter 24.1: heap-Str primitive externs for the `show` instances
|
||||
// — `ailang_bool_to_str` (Show Bool) and `ailang_str_clone`
|
||||
// (Show Str). Same dual-realisation ABI as int_to_str /
|
||||
// float_to_str: rc_header at offset -8 on the heap-Str output;
|
||||
// consumer ABI shared with static-Str (len at offset 0, bytes
|
||||
// at offset 8). Declared unconditionally on the same rationale
|
||||
// — runtime/str.c is unconditionally linked and clang -O2 dead-
|
||||
// strips when no caller exists.
|
||||
out.push_str("declare ptr @ailang_bool_to_str(i1)\n");
|
||||
out.push_str("declare ptr @ailang_str_clone(ptr)\n");
|
||||
// Floats iter 4.4: saturating fp-to-int intrinsic for
|
||||
// float_to_int_truncate. NaN → 0, +Inf → i64::MAX, -Inf →
|
||||
// i64::MIN, finite-out-of-range saturates, finite-in-range
|
||||
@@ -1931,6 +1941,40 @@ impl<'a> Emitter<'a> {
|
||||
));
|
||||
return Ok((dst, "ptr".to_string()));
|
||||
}
|
||||
if name == "bool_to_str" {
|
||||
// Iter 24.1: lowers to the runtime C glue
|
||||
// `ailang_bool_to_str(i1) -> ptr` defined in
|
||||
// `runtime/str.c`. Returned pointer is a heap-Str
|
||||
// (rc_header at offset -8; consumer ABI shared with
|
||||
// static-Str). Used by `show__Bool` in milestone 24.
|
||||
if args.len() != 1 {
|
||||
return Err(CodegenError::Internal("bool_to_str arity".into()));
|
||||
}
|
||||
let (a, _) = self.lower_term(&args[0])?;
|
||||
let dst = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {dst} = call ptr @ailang_bool_to_str(i1 {a})\n"
|
||||
));
|
||||
return Ok((dst, "ptr".to_string()));
|
||||
}
|
||||
if name == "str_clone" {
|
||||
// Iter 24.1: lowers to the runtime C glue
|
||||
// `ailang_str_clone(ptr) -> ptr` defined in
|
||||
// `runtime/str.c`. Reads `len` from offset 0 of the
|
||||
// source Str payload and allocates a fresh heap-Str
|
||||
// slab; works uniformly on static-Str and heap-Str
|
||||
// inputs because the consumer ABI is identical. Used by
|
||||
// `show__Str` in milestone 24.
|
||||
if args.len() != 1 {
|
||||
return Err(CodegenError::Internal("str_clone arity".into()));
|
||||
}
|
||||
let (a, _) = self.lower_term(&args[0])?;
|
||||
let dst = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {dst} = call ptr @ailang_str_clone(ptr {a})\n"
|
||||
));
|
||||
return Ok((dst, "ptr".to_string()));
|
||||
}
|
||||
|
||||
// Cross-module call: exactly one dot in the name → resolve via import map.
|
||||
// Logic identical to the typechecker (see `synth` for `Term::Var`).
|
||||
@@ -2131,6 +2175,8 @@ impl<'a> Emitter<'a> {
|
||||
| "is_nan"
|
||||
| "float_to_str"
|
||||
| "int_to_str"
|
||||
| "bool_to_str"
|
||||
| "str_clone"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -4202,4 +4248,84 @@ mod tests {
|
||||
"expected lowering of float_to_str to call @ailang_float_to_str; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 24.1: a `Term::App` calling `bool_to_str` lowers to
|
||||
/// `call ptr @ailang_bool_to_str(i1 %a)`. Pins the new builtin's
|
||||
/// lowering shape against the runtime C glue introduced in this
|
||||
/// iter (mirror of hs.4's `int_to_str_lowers_to_ailang_int_to_str_call`).
|
||||
#[test]
|
||||
fn bool_to_str_emits_call_to_ailang_bool_to_str() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
imports: vec![],
|
||||
defs: vec![Def::Fn(FnDef {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
op: "io/print_str".into(),
|
||||
args: vec![Term::App {
|
||||
callee: Box::new(Term::Var { name: "bool_to_str".into() }),
|
||||
args: vec![Term::Lit { lit: Literal::Bool { value: true } }],
|
||||
tail: false,
|
||||
}],
|
||||
tail: false,
|
||||
},
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
})],
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains("call ptr @ailang_bool_to_str(i1 "),
|
||||
"expected lowering of bool_to_str to call @ailang_bool_to_str; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter 24.1: a `Term::App` calling `str_clone` lowers to
|
||||
/// `call ptr @ailang_str_clone(ptr %a)`. Pins the new builtin's
|
||||
/// lowering shape against the runtime C glue introduced in this
|
||||
/// iter (mirror of hs.4's `int_to_str_lowers_to_ailang_int_to_str_call`).
|
||||
#[test]
|
||||
fn str_clone_emits_call_to_ailang_str_clone() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
imports: vec![],
|
||||
defs: vec![Def::Fn(FnDef {
|
||||
name: "main".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![],
|
||||
ret: Box::new(Type::unit()),
|
||||
effects: vec!["IO".into()],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Implicit,
|
||||
},
|
||||
params: vec![],
|
||||
body: Term::Do {
|
||||
op: "io/print_str".into(),
|
||||
args: vec![Term::App {
|
||||
callee: Box::new(Term::Var { name: "str_clone".into() }),
|
||||
args: vec![Term::Lit { lit: Literal::Str { value: "hi".into() } }],
|
||||
tail: false,
|
||||
}],
|
||||
tail: false,
|
||||
},
|
||||
suppress: vec![],
|
||||
doc: None,
|
||||
})],
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains("call ptr @ailang_str_clone(ptr "),
|
||||
"expected lowering of str_clone to call @ailang_str_clone; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +178,20 @@ pub(crate) fn builtin_ail_type(name: &str) -> Option<Type> {
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
"bool_to_str" => Type::Fn {
|
||||
params: vec![Type::bool_()],
|
||||
ret: Box::new(Type::str_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![],
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
"str_clone" => Type::Fn {
|
||||
params: vec![Type::str_()],
|
||||
ret: Box::new(Type::str_()),
|
||||
effects: vec![],
|
||||
param_modes: vec![ParamMode::Borrow],
|
||||
ret_mode: ParamMode::Own,
|
||||
},
|
||||
"is_nan" => Type::Fn {
|
||||
params: vec![Type::float()],
|
||||
ret: Box::new(Type::bool_()),
|
||||
|
||||
Reference in New Issue
Block a user