diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index dd13021..a7ac544 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -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}"); + } } diff --git a/crates/ailang-codegen/src/synth.rs b/crates/ailang-codegen/src/synth.rs index b014b6f..daa8448 100644 --- a/crates/ailang-codegen/src/synth.rs +++ b/crates/ailang-codegen/src/synth.rs @@ -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, } }