diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 0e46f1e..13e9eab 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -53,7 +53,7 @@ use subst::{ qualify_local_types_codegen, unify_for_subst, }; use synth::{ - builtin_ail_type, builtin_binop, builtin_effect_op_ret, c_byte_len, default_triple, + builtin_ail_type, builtin_binop_typed, builtin_effect_op_ret, c_byte_len, default_triple, fn_sig_from_type, ll_string_literal, llvm_type, }; @@ -1733,25 +1733,42 @@ impl<'a> Emitter<'a> { return self.lower_eq(&arg_ty, &a, &b, &a_ll); } - // Built-in arithmetic / comparison. - if let Some((instr, ret_ty)) = builtin_binop(name) { + // Floats iter 4.2: arithmetic / comparison are now polymorphic + // over `{Int, Float}`. Resolve the arg type, then dispatch via + // `builtin_binop_typed`. Same shape as the `==` dispatch above. + // Comparison ops are matched here in iter 4.2 with Int-only + // dispatch (preserving pre-iter-4 behaviour) so the workspace + // stays green; iter 4.3 adds the Float comparison arms. + if matches!(name, "+" | "-" | "*" | "/" | "%" | "!=" | "<" | "<=" | ">" | ">=") { if args.len() != 2 { return Err(CodegenError::Internal(format!( "builtin `{name}` expected 2 args" ))); } + let arg_ty = self.synth_arg_type(&args[0])?; + let (instr, ll_ty) = builtin_binop_typed(name, &arg_ty) + .ok_or_else(|| CodegenError::Internal(format!( + "`{name}` not supported for type `{}`", + ailang_core::pretty::type_to_string(&arg_ty) + )))?; let (a, _) = self.lower_term(&args[0])?; let (b, _) = self.lower_term(&args[1])?; let dst = self.fresh_ssa(); self.body.push_str(&format!( - " {dst} = {instr} i64 {a}, {b}\n" + " {dst} = {instr} {ll_ty} {a}, {b}\n" )); // Builtins are not function calls in LLVM (they're inline // arithmetic); `tail` annotation has nothing to act on. // The typechecker accepts the marker but it is a no-op // here. (Iter 14e survey: no fixture marks a builtin tail.) let _ = tail; - return Ok((dst, ret_ty.into())); + // Comparison ops return i1; arithmetic returns the operand type. + let result_ll_ty = if matches!(name, "!=" | "<" | "<=" | ">" | ">=") { + "i1".into() + } else { + ll_ty.into() + }; + return Ok((dst, result_ll_ty)); } if name == "not" { if args.len() != 1 { @@ -2073,7 +2090,7 @@ impl<'a> Emitter<'a> { /// `prefix.def`. Locals shadow this — the caller checks for /// that first. fn is_static_callee(&self, name: &str) -> bool { - if builtin_binop(name).is_some() || name == "not" { + if matches!(name, "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<" | "<=" | ">" | ">=") || name == "not" { return true; } if name.matches('.').count() == 1 { @@ -2918,4 +2935,64 @@ mod tests { "ir missing the Float literal lowering: {ir}" ); } + + /// Floats iter 4.2 RED: `(+ 1.5 2.5)` lowers as `fadd double`, + /// not `add i64`. The Int regression `(+ 1 2)` still lowers as + /// `add i64`. Both lowerings live in one IR for one workspace + /// build. + #[test] + fn lowers_float_arithmetic_dispatched() { + 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, + }) + } + let plus_int = Term::App { + callee: Box::new(Term::Var { name: "+".into() }), + args: vec![ + Term::Lit { lit: Literal::Int { value: 1 } }, + Term::Lit { lit: Literal::Int { value: 2 } }, + ], + tail: false, + }; + let plus_float = Term::App { + callee: Box::new(Term::Var { name: "+".into() }), + args: vec![ + Term::Lit { lit: Literal::Float { bits: 0x3ff8_0000_0000_0000u64 } }, + Term::Lit { lit: Literal::Float { bits: 0x4004_0000_0000_0000u64 } }, + ], + tail: false, + }; + let m = Module { + schema: ailang_core::SCHEMA.to_string(), + name: "t".into(), + imports: vec![], + defs: vec![ + fn_def("ai", plus_int, Type::int()), + fn_def("af", plus_float, Type::float()), + fn_def("main", Term::Lit { lit: Literal::Unit }, Type::unit()), + ], + }; + let ir = emit_ir(&m).unwrap(); + assert!( + ir.contains("add i64"), + "Int arithmetic regressed (no `add i64` in IR): {ir}" + ); + assert!( + ir.contains("fadd double"), + "Float arithmetic missing (no `fadd double` in IR): {ir}" + ); + } } diff --git a/crates/ailang-codegen/src/synth.rs b/crates/ailang-codegen/src/synth.rs index cf82811..553067a 100644 --- a/crates/ailang-codegen/src/synth.rs +++ b/crates/ailang-codegen/src/synth.rs @@ -239,21 +239,37 @@ pub(crate) fn type_descriptor(t: &Type) -> String { } } -pub(crate) fn builtin_binop(name: &str) -> Option<(&'static str, &'static str)> { - Some(match name { - "+" => ("add", "i64"), - "-" => ("sub", "i64"), - "*" => ("mul", "i64"), - "/" => ("sdiv", "i64"), - "%" => ("srem", "i64"), - "==" => ("icmp eq", "i1"), - "!=" => ("icmp ne", "i1"), - "<" => ("icmp slt", "i1"), - "<=" => ("icmp sle", "i1"), - ">" => ("icmp sgt", "i1"), - ">=" => ("icmp sge", "i1"), - _ => return None, - }) +/// Floats iter 4.2: arithmetic / comparison ops are type-dispatched +/// over `{Int, Float}`. Caller (`lower_app`) resolves the arg type +/// via `synth_arg_type` and passes it here. Returns the +/// `(instruction, llvm_type)` pair to emit. `%` stays Int-only. +/// +/// Comparison-op Int arms are kept here in iter 4.2 to preserve +/// the no-regression invariant — pre-iter-4 codegen routed +/// `<`/`<=`/`>`/`>=`/`!=` through the same Int-only `builtin_binop` +/// table. Iter 4.3 adds the Float arms. +pub(crate) fn builtin_binop_typed(name: &str, arg_ty: &Type) -> Option<(&'static str, &'static str)> { + let is_int = matches!(arg_ty, Type::Con { name, .. } if name == "Int"); + let is_float = matches!(arg_ty, Type::Con { name, .. } if name == "Float"); + match (name, is_int, is_float) { + ("+", true, _) => Some(("add", "i64")), + ("+", _, true) => Some(("fadd", "double")), + ("-", true, _) => Some(("sub", "i64")), + ("-", _, true) => Some(("fsub", "double")), + ("*", true, _) => Some(("mul", "i64")), + ("*", _, true) => Some(("fmul", "double")), + ("/", true, _) => Some(("sdiv", "i64")), + ("/", _, true) => Some(("fdiv", "double")), + ("%", true, _) => Some(("srem", "i64")), + // Int-arm comparisons preserved from pre-iter-4 `builtin_binop`. + // Float arms land in iter 4.3. + ("!=", true, _) => Some(("icmp ne", "i64")), + ("<", true, _) => Some(("icmp slt", "i64")), + ("<=", true, _) => Some(("icmp sle", "i64")), + (">", true, _) => Some(("icmp sgt", "i64")), + (">=", true, _) => Some(("icmp sge", "i64")), + _ => None, + } } pub(crate) fn c_byte_len(s: &str) -> usize {