floats iter 4.3: codegen Float comparison arms (fcmp olt/ole/ogt/oge/UNE) + lower_eq Float

This commit is contained in:
2026-05-10 16:10:13 +02:00
parent 2a290704df
commit 3869641a31
2 changed files with 82 additions and 0 deletions
+73
View File
@@ -2320,6 +2320,13 @@ impl<'a> Emitter<'a> {
let _ = b;
Ok(("true".into(), "i1".into()))
}
"Float" => {
let dst = self.fresh_ssa();
self.body.push_str(&format!(
" {dst} = fcmp oeq double {a}, {b}\n"
));
Ok((dst, "i1".into()))
}
other => Err(CodegenError::Internal(format!(
"`==` not supported for type `{other}` \
(ADT and user-defined types lack a structural-equality scheme)"
@@ -3003,4 +3010,70 @@ mod tests {
"Float arithmetic missing (no `fadd double` in IR): {ir}"
);
}
/// Floats iter 4.3 RED: `(< 1.5 2.5)` lowers as `fcmp olt double`;
/// `(!= 1.5 1.5)` lowers as `fcmp UNE double` (NOT `one`); `(== 1.5
/// 1.5)` lowers as `fcmp oeq double`. Int regressions still emit
/// `icmp slt i64` / `icmp ne i64` / `icmp eq i64`.
#[test]
fn lowers_float_comparison_dispatched() {
use ailang_core::ast::*;
fn fn_def(name: &str, body: Term) -> Def {
Def::Fn(FnDef {
name: name.into(),
ty: Type::Fn {
params: vec![],
ret: Box::new(Type::bool_()),
effects: vec![],
param_modes: vec![],
ret_mode: ParamMode::Implicit,
},
params: vec![],
body,
suppress: vec![],
doc: None,
})
}
fn cmp(op: &str, a: Term, b: Term) -> Term {
Term::App {
callee: Box::new(Term::Var { name: op.into() }),
args: vec![a, b],
tail: false,
}
}
let f1 = Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } };
let f2 = Term::Lit { lit: Literal::Float { bits: 0x4004_0000_0000_0000u64 } };
let i1 = Term::Lit { lit: Literal::Int { value: 1 } };
let i2 = Term::Lit { lit: Literal::Int { value: 2 } };
let main_def = Def::Fn(FnDef {
name: "main".into(),
ty: Type::Fn {
params: vec![], ret: Box::new(Type::unit()), effects: vec![],
param_modes: vec![], ret_mode: ParamMode::Implicit,
},
params: vec![], body: Term::Lit { lit: Literal::Unit },
suppress: vec![], doc: None,
});
let m = Module {
schema: ailang_core::SCHEMA.to_string(),
name: "t".into(),
imports: vec![],
defs: vec![
fn_def("flt_f", cmp("<", f1.clone(), f2.clone())),
fn_def("fne_f", cmp("!=", f1.clone(), f1.clone())),
fn_def("feq_f", cmp("==", f1.clone(), f1.clone())),
fn_def("flt_i", cmp("<", i1.clone(), i2.clone())),
fn_def("fne_i", cmp("!=", i1.clone(), i2.clone())),
fn_def("feq_i", cmp("==", i1.clone(), i2.clone())),
main_def,
],
};
let ir = emit_ir(&m).unwrap();
assert!(ir.contains("fcmp olt double"), "missing `fcmp olt double`: {ir}");
assert!(ir.contains("fcmp une double"), "missing `fcmp une double` (note: `une` not `one`): {ir}");
assert!(ir.contains("fcmp oeq double"), "missing `fcmp oeq double`: {ir}");
assert!(ir.contains("icmp slt i64"), "Int `<` regressed: {ir}");
assert!(ir.contains("icmp ne i64"), "Int `!=` regressed: {ir}");
assert!(ir.contains("icmp eq i64"), "Int `==` regressed: {ir}");
}
}
+9
View File
@@ -281,6 +281,15 @@ pub(crate) fn builtin_binop_typed(
("<=", true, _) => Some(("icmp sle", "i64", "i1")),
(">", true, _) => Some(("icmp sgt", "i64", "i1")),
(">=", true, _) => Some(("icmp sge", "i64", "i1")),
// Float comparison arms (Floats iter 4.3). Note: `!=` Float
// uses `fcmp une` ("unordered or not equal") — NOT `one`
// ("ordered and not equal"), which would return `false` for
// `nan != nan` and violate IEEE / spec A5.
("!=", _, true) => Some(("fcmp une", "double", "i1")),
("<", _, true) => Some(("fcmp olt", "double", "i1")),
("<=", _, true) => Some(("fcmp ole", "double", "i1")),
(">", _, true) => Some(("fcmp ogt", "double", "i1")),
(">=", _, true) => Some(("fcmp oge", "double", "i1")),
_ => None,
}
}