floats iter 4.2 fixup: 3-tuple return for builtin_binop_typed + classifier helper

This commit is contained in:
2026-05-10 16:07:11 +02:00
parent 8044a4d98c
commit 2a290704df
2 changed files with 50 additions and 29 deletions
+19 -11
View File
@@ -57,6 +57,20 @@ use synth::{
fn_sig_from_type, ll_string_literal, llvm_type, 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`]. /// Failure modes of [`emit_ir`] / [`lower_workspace`].
/// ///
/// Most variants signal a compiler invariant violation rather than a /// 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 // Comparison ops are matched here in iter 4.2 with Int-only
// dispatch (preserving pre-iter-4 behaviour) so the workspace // dispatch (preserving pre-iter-4 behaviour) so the workspace
// stays green; iter 4.3 adds the Float comparison arms. // stays green; iter 4.3 adds the Float comparison arms.
if matches!(name, "+" | "-" | "*" | "/" | "%" | "!=" | "<" | "<=" | ">" | ">=") { if is_arithmetic_or_comparison_op(name) {
if args.len() != 2 { if args.len() != 2 {
return Err(CodegenError::Internal(format!( return Err(CodegenError::Internal(format!(
"builtin `{name}` expected 2 args" "builtin `{name}` expected 2 args"
))); )));
} }
let arg_ty = self.synth_arg_type(&args[0])?; 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!( .ok_or_else(|| CodegenError::Internal(format!(
"`{name}` not supported for type `{}`", "`{name}` not supported for type `{}`",
ailang_core::pretty::type_to_string(&arg_ty) ailang_core::pretty::type_to_string(&arg_ty)
@@ -1755,20 +1769,14 @@ impl<'a> Emitter<'a> {
let (b, _) = self.lower_term(&args[1])?; let (b, _) = self.lower_term(&args[1])?;
let dst = self.fresh_ssa(); let dst = self.fresh_ssa();
self.body.push_str(&format!( 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 // Builtins are not function calls in LLVM (they're inline
// arithmetic); `tail` annotation has nothing to act on. // arithmetic); `tail` annotation has nothing to act on.
// The typechecker accepts the marker but it is a no-op // The typechecker accepts the marker but it is a no-op
// here. (Iter 14e survey: no fixture marks a builtin tail.) // here. (Iter 14e survey: no fixture marks a builtin tail.)
let _ = tail; let _ = tail;
// Comparison ops return i1; arithmetic returns the operand type. return Ok((dst, result_ll_ty.into()));
let result_ll_ty = if matches!(name, "!=" | "<" | "<=" | ">" | ">=") {
"i1".into()
} else {
ll_ty.into()
};
return Ok((dst, result_ll_ty));
} }
if name == "not" { if name == "not" {
if args.len() != 1 { if args.len() != 1 {
@@ -2090,7 +2098,7 @@ impl<'a> Emitter<'a> {
/// `prefix.def`. Locals shadow this — the caller checks for /// `prefix.def`. Locals shadow this — the caller checks for
/// that first. /// that first.
fn is_static_callee(&self, name: &str) -> bool { 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; return true;
} }
if name.matches('.').count() == 1 { if name.matches('.').count() == 1 {
+31 -18
View File
@@ -242,32 +242,45 @@ pub(crate) fn type_descriptor(t: &Type) -> String {
/// Floats iter 4.2: arithmetic / comparison ops are type-dispatched /// Floats iter 4.2: arithmetic / comparison ops are type-dispatched
/// over `{Int, Float}`. Caller (`lower_app`) resolves the arg type /// over `{Int, Float}`. Caller (`lower_app`) resolves the arg type
/// via `synth_arg_type` and passes it here. Returns the /// 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 /// Comparison-op Int arms are kept here in iter 4.2 to preserve
/// the no-regression invariant — pre-iter-4 codegen routed /// the no-regression invariant — pre-iter-4 codegen routed
/// `<`/`<=`/`>`/`>=`/`!=` through the same Int-only `builtin_binop` /// `<`/`<=`/`>`/`>=`/`!=` through the same Int-only `builtin_binop`
/// table. Iter 4.3 adds the Float arms. /// table. Iter 4.3 adds the Float arms (`("fcmp olt", "double", "i1")`
pub(crate) fn builtin_binop_typed(name: &str, arg_ty: &Type) -> Option<(&'static str, &'static str)> { /// 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_int = matches!(arg_ty, Type::Con { name, .. } if name == "Int");
let is_float = matches!(arg_ty, Type::Con { name, .. } if name == "Float"); let is_float = matches!(arg_ty, Type::Con { name, .. } if name == "Float");
match (name, is_int, is_float) { match (name, is_int, is_float) {
("+", true, _) => Some(("add", "i64")), ("+", true, _) => Some(("add", "i64", "i64")),
("+", _, true) => Some(("fadd", "double")), ("+", _, true) => Some(("fadd", "double", "double")),
("-", true, _) => Some(("sub", "i64")), ("-", true, _) => Some(("sub", "i64", "i64")),
("-", _, true) => Some(("fsub", "double")), ("-", _, true) => Some(("fsub", "double", "double")),
("*", true, _) => Some(("mul", "i64")), ("*", true, _) => Some(("mul", "i64", "i64")),
("*", _, true) => Some(("fmul", "double")), ("*", _, true) => Some(("fmul", "double", "double")),
("/", true, _) => Some(("sdiv", "i64")), ("/", true, _) => Some(("sdiv", "i64", "i64")),
("/", _, true) => Some(("fdiv", "double")), ("/", _, true) => Some(("fdiv", "double", "double")),
("%", true, _) => Some(("srem", "i64")), ("%", true, _) => Some(("srem", "i64", "i64")),
// Int-arm comparisons preserved from pre-iter-4 `builtin_binop`. // 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. // Float arms land in iter 4.3.
("!=", true, _) => Some(("icmp ne", "i64")), ("!=", true, _) => Some(("icmp ne", "i64", "i1")),
("<", true, _) => Some(("icmp slt", "i64")), ("<", true, _) => Some(("icmp slt", "i64", "i1")),
("<=", true, _) => Some(("icmp sle", "i64")), ("<=", true, _) => Some(("icmp sle", "i64", "i1")),
(">", true, _) => Some(("icmp sgt", "i64")), (">", true, _) => Some(("icmp sgt", "i64", "i1")),
(">=", true, _) => Some(("icmp sge", "i64")), (">=", true, _) => Some(("icmp sge", "i64", "i1")),
_ => None, _ => None,
} }
} }