floats iter 4.4: codegen neg/int_to_float/float_to_int_truncate/is_nan + float_to_str-deferred

This commit is contained in:
2026-05-10 16:15:39 +02:00
parent 3869641a31
commit 581144a4f9
6 changed files with 140 additions and 1 deletions
+1
View File
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
declare i32 @puts(ptr)
declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_hello_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_hello_main_adapter, ptr null }
define i8 @ail_hello_main() {
+1
View File
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
declare i32 @puts(ptr)
declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_list_sum_list_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_sum_list_adapter, ptr null }
@ail_list_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_main_adapter, ptr null }
+1
View File
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
declare i32 @puts(ptr)
declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_max3_max_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max_adapter, ptr null }
@ail_max3_max3_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max3_adapter, ptr null }
+1
View File
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
declare i32 @puts(ptr)
declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_sum_sum_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_sum_adapter, ptr null }
@ail_sum_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_main_adapter, ptr null }
+1
View File
@@ -8,6 +8,7 @@ declare i32 @printf(ptr, ...)
declare i32 @puts(ptr)
declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null }
@ail_ws_main_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_main_main_adapter, ptr null }
+135 -1
View File
@@ -472,7 +472,13 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
// Iter 16e: `==` on `Str` lowers to `@strcmp` followed by
// `icmp eq i32 0`. NUL-terminated strings make this a one-liner;
// libc supplies `strcmp` so no extra link flag is needed.
out.push_str("declare i32 @strcmp(ptr, ptr)\n\n");
out.push_str("declare i32 @strcmp(ptr, 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
// truncates toward zero. LLVM 12+, always available with
// clang 22.
out.push_str("declare i64 @llvm.fptosi.sat.i64.f64(double)\n\n");
out.push_str(&header);
out.push_str(&body);
@@ -1789,6 +1795,73 @@ impl<'a> Emitter<'a> {
return Ok((dst, "i1".into()));
}
// Floats iter 4.4: polymorphic neg + 3 monomorphic fn builtins.
if name == "neg" {
if args.len() != 1 {
return Err(CodegenError::Internal("neg arity".into()));
}
let arg_ty = self.synth_arg_type(&args[0])?;
let (a, _) = self.lower_term(&args[0])?;
let dst = self.fresh_ssa();
match &arg_ty {
Type::Con { name, .. } if name == "Int" => {
self.body.push_str(&format!(" {dst} = sub i64 0, {a}\n"));
return Ok((dst, "i64".into()));
}
Type::Con { name, .. } if name == "Float" => {
// LLVM 8+ `fneg` correctly handles -0.0.
self.body.push_str(&format!(" {dst} = fneg double {a}\n"));
return Ok((dst, "double".into()));
}
other => return Err(CodegenError::Internal(format!(
"`neg` not supported for type `{}`",
ailang_core::pretty::type_to_string(other)
))),
}
}
if name == "int_to_float" {
if args.len() != 1 {
return Err(CodegenError::Internal("int_to_float arity".into()));
}
let (a, _) = self.lower_term(&args[0])?;
let dst = self.fresh_ssa();
self.body.push_str(&format!(" {dst} = sitofp i64 {a} to double\n"));
return Ok((dst, "double".into()));
}
if name == "float_to_int_truncate" {
if args.len() != 1 {
return Err(CodegenError::Internal("float_to_int_truncate arity".into()));
}
let (a, _) = self.lower_term(&args[0])?;
let dst = self.fresh_ssa();
self.body.push_str(&format!(
" {dst} = call i64 @llvm.fptosi.sat.i64.f64(double {a})\n"
));
return Ok((dst, "i64".into()));
}
if name == "is_nan" {
if args.len() != 1 {
return Err(CodegenError::Internal("is_nan arity".into()));
}
let (a, _) = self.lower_term(&args[0])?;
let dst = self.fresh_ssa();
// `fcmp uno x, x` returns `i1 1` iff `x` is NaN — only
// NaN compares unordered against itself.
self.body.push_str(&format!(" {dst} = fcmp uno double {a}, {a}\n"));
return Ok((dst, "i1".into()));
}
if name == "float_to_str" {
// Deferred to iter 5+: needs runtime-allocated Str
// (currently the codegen Str path uses only static
// `@.str_*` globals; no malloc-backed dynamic-Str
// infrastructure). The typecheck path in iter 3
// installs `float_to_str : (Float) -> Str`; until the
// runtime gets a Str allocator, calling it crashes
// codegen. This is intentional: don't ship an
// unimplemented call to the LLM-author surface.
unimplemented!("Floats milestone iter 5+: float_to_str needs dynamic Str allocation");
}
// Cross-module call: exactly one dot in the name → resolve via import map.
// Logic identical to the typechecker (see `synth` for `Term::Var`).
if name.matches('.').count() == 1 {
@@ -2101,6 +2174,15 @@ impl<'a> Emitter<'a> {
if is_arithmetic_or_comparison_op(name) || name == "==" || name == "not" {
return true;
}
// Floats iter 4.4: new fn-builtins (`neg`, `int_to_float`,
// `float_to_int_truncate`, `is_nan`, `float_to_str`) lower
// inline in `lower_app`, parallel to the operator path.
if matches!(
name,
"neg" | "int_to_float" | "float_to_int_truncate" | "is_nan" | "float_to_str"
) {
return true;
}
if name.matches('.').count() == 1 {
return true;
}
@@ -3076,4 +3158,56 @@ mod tests {
assert!(ir.contains("icmp ne i64"), "Int `!=` regressed: {ir}");
assert!(ir.contains("icmp eq i64"), "Int `==` regressed: {ir}");
}
/// Floats iter 4.4 RED: four new fn-builtins lower to the spec'd
/// LLVM ops. `neg` polymorphic dispatches to `sub i64 0, %x` for
/// Int and `fneg double %x` for Float (NOT `fsub 0.0, %x`, which
/// is wrong for `-0.0`). `int_to_float` → `sitofp`. `is_nan` →
/// `fcmp uno double %x, %x`. `float_to_int_truncate` →
/// `@llvm.fptosi.sat.i64.f64` intrinsic call.
#[test]
fn lowers_float_fn_builtins() {
use ailang_core::ast::*;
fn fn_def(name: &str, body: Term, ret_ty: Type) -> Def {
Def::Fn(FnDef {
name: name.into(),
ty: Type::Fn {
params: vec![], ret: Box::new(ret_ty), effects: vec![],
param_modes: vec![], ret_mode: ParamMode::Implicit,
},
params: vec![], body, suppress: vec![], doc: None,
})
}
fn app1(callee: &str, arg: Term) -> Term {
Term::App {
callee: Box::new(Term::Var { name: callee.into() }),
args: vec![arg], tail: false,
}
}
let neg_int = app1("neg", Term::Lit { lit: Literal::Int { value: 5 } });
let neg_float = app1("neg", Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } });
let i2f = app1("int_to_float", Term::Lit { lit: Literal::Int { value: 5 } });
let f2i = app1("float_to_int_truncate", Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } });
let isnan = app1("is_nan", Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } });
let main_def = fn_def("main", Term::Lit { lit: Literal::Unit }, Type::unit());
let m = Module {
schema: ailang_core::SCHEMA.to_string(),
name: "t".into(),
imports: vec![],
defs: vec![
fn_def("ni", neg_int, Type::int()),
fn_def("nf", neg_float, Type::float()),
fn_def("c1", i2f, Type::float()),
fn_def("c2", f2i, Type::int()),
fn_def("isn", isnan, Type::bool_()),
main_def,
],
};
let ir = emit_ir(&m).unwrap();
assert!(ir.contains("sub i64 0,"), "neg Int missing: {ir}");
assert!(ir.contains("fneg double"), "neg Float missing (must use fneg, not fsub-from-zero): {ir}");
assert!(ir.contains("sitofp i64"), "int_to_float missing: {ir}");
assert!(ir.contains("@llvm.fptosi.sat.i64.f64"), "float_to_int_truncate intrinsic missing: {ir}");
assert!(ir.contains("fcmp uno double"), "is_nan missing (must be fcmp uno x, x): {ir}");
}
}