From 2a290704df6b453f7a73bf420da0bf6229a1ff1c Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 16:07:11 +0200 Subject: [PATCH] floats iter 4.2 fixup: 3-tuple return for builtin_binop_typed + classifier helper --- crates/ailang-codegen/src/lib.rs | 30 +++++++++++------- crates/ailang-codegen/src/synth.rs | 49 +++++++++++++++++++----------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 13e9eab..dd13021 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -57,6 +57,20 @@ use synth::{ fn_sig_from_type, ll_string_literal, llvm_type, }; +/// Floats iter 4.2 fixup: classify a builtin name as a polymorphic +/// arithmetic or comparison op (the set `+`, `-`, `*`, `/`, `%`, +/// `!=`, `<`, `<=`, `>`, `>=`). `==` is NOT in this set — it goes +/// through `lower_eq` separately. Used by `lower_app` to decide +/// whether to call `builtin_binop_typed`, and by `is_static_callee` +/// (in combination with `==` and `not`) to recognise built-in +/// callees during the global-resolution pass. +fn is_arithmetic_or_comparison_op(name: &str) -> bool { + matches!( + name, + "+" | "-" | "*" | "/" | "%" | "!=" | "<" | "<=" | ">" | ">=" + ) +} + /// Failure modes of [`emit_ir`] / [`lower_workspace`]. /// /// Most variants signal a compiler invariant violation rather than a @@ -1739,14 +1753,14 @@ impl<'a> Emitter<'a> { // 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 is_arithmetic_or_comparison_op(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) + let (instr, operand_ll_ty, result_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) @@ -1755,20 +1769,14 @@ impl<'a> Emitter<'a> { let (b, _) = self.lower_term(&args[1])?; let dst = self.fresh_ssa(); self.body.push_str(&format!( - " {dst} = {instr} {ll_ty} {a}, {b}\n" + " {dst} = {instr} {operand_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; - // 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)); + return Ok((dst, result_ll_ty.into())); } if name == "not" { if args.len() != 1 { @@ -2090,7 +2098,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 matches!(name, "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<" | "<=" | ">" | ">=") || name == "not" { + if is_arithmetic_or_comparison_op(name) || name == "==" || name == "not" { return true; } if name.matches('.').count() == 1 { diff --git a/crates/ailang-codegen/src/synth.rs b/crates/ailang-codegen/src/synth.rs index 553067a..b014b6f 100644 --- a/crates/ailang-codegen/src/synth.rs +++ b/crates/ailang-codegen/src/synth.rs @@ -242,32 +242,45 @@ pub(crate) fn type_descriptor(t: &Type) -> String { /// 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. +/// `(instruction, operand_llvm_type, result_llvm_type)` triple to +/// emit. `%` stays Int-only. +/// +/// The triple carries operand and result types separately because +/// comparison ops produce `i1` regardless of operand width +/// (e.g. `icmp slt i64 ..., ... -> i1`); arithmetic produces the +/// same type as its operands. Keeping both in the table localises +/// the comparison-vs-arithmetic distinction here — the caller no +/// longer has to second-guess which arm fired. /// /// 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)> { +/// table. Iter 4.3 adds the Float arms (`("fcmp olt", "double", "i1")` +/// and friends). +pub(crate) fn builtin_binop_typed( + name: &str, + arg_ty: &Type, +) -> Option<(&'static str, &'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`. + ("+", true, _) => Some(("add", "i64", "i64")), + ("+", _, true) => Some(("fadd", "double", "double")), + ("-", true, _) => Some(("sub", "i64", "i64")), + ("-", _, true) => Some(("fsub", "double", "double")), + ("*", true, _) => Some(("mul", "i64", "i64")), + ("*", _, true) => Some(("fmul", "double", "double")), + ("/", true, _) => Some(("sdiv", "i64", "i64")), + ("/", _, true) => Some(("fdiv", "double", "double")), + ("%", true, _) => Some(("srem", "i64", "i64")), + // Comparison ops: operand types differ, result is always `i1`. + // 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")), + ("!=", true, _) => Some(("icmp ne", "i64", "i1")), + ("<", true, _) => Some(("icmp slt", "i64", "i1")), + ("<=", true, _) => Some(("icmp sle", "i64", "i1")), + (">", true, _) => Some(("icmp sgt", "i64", "i1")), + (">=", true, _) => Some(("icmp sge", "i64", "i1")), _ => None, } }