floats iter 4.2: codegen arithmetic dispatch on arg type — fadd/fsub/fmul/fdiv double
This commit is contained in:
@@ -53,7 +53,7 @@ use subst::{
|
|||||||
qualify_local_types_codegen, unify_for_subst,
|
qualify_local_types_codegen, unify_for_subst,
|
||||||
};
|
};
|
||||||
use synth::{
|
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,
|
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);
|
return self.lower_eq(&arg_ty, &a, &b, &a_ll);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Built-in arithmetic / comparison.
|
// Floats iter 4.2: arithmetic / comparison are now polymorphic
|
||||||
if let Some((instr, ret_ty)) = builtin_binop(name) {
|
// 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 {
|
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 (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 (a, _) = self.lower_term(&args[0])?;
|
||||||
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} i64 {a}, {b}\n"
|
" {dst} = {instr} {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;
|
||||||
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 name == "not" {
|
||||||
if args.len() != 1 {
|
if args.len() != 1 {
|
||||||
@@ -2073,7 +2090,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 builtin_binop(name).is_some() || name == "not" {
|
if matches!(name, "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<" | "<=" | ">" | ">=") || name == "not" {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if name.matches('.').count() == 1 {
|
if name.matches('.').count() == 1 {
|
||||||
@@ -2918,4 +2935,64 @@ mod tests {
|
|||||||
"ir missing the Float literal lowering: {ir}"
|
"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}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -239,21 +239,37 @@ pub(crate) fn type_descriptor(t: &Type) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn builtin_binop(name: &str) -> Option<(&'static str, &'static str)> {
|
/// Floats iter 4.2: arithmetic / comparison ops are type-dispatched
|
||||||
Some(match name {
|
/// over `{Int, Float}`. Caller (`lower_app`) resolves the arg type
|
||||||
"+" => ("add", "i64"),
|
/// via `synth_arg_type` and passes it here. Returns the
|
||||||
"-" => ("sub", "i64"),
|
/// `(instruction, llvm_type)` pair to emit. `%` stays Int-only.
|
||||||
"*" => ("mul", "i64"),
|
///
|
||||||
"/" => ("sdiv", "i64"),
|
/// Comparison-op Int arms are kept here in iter 4.2 to preserve
|
||||||
"%" => ("srem", "i64"),
|
/// the no-regression invariant — pre-iter-4 codegen routed
|
||||||
"==" => ("icmp eq", "i1"),
|
/// `<`/`<=`/`>`/`>=`/`!=` through the same Int-only `builtin_binop`
|
||||||
"!=" => ("icmp ne", "i1"),
|
/// table. Iter 4.3 adds the Float arms.
|
||||||
"<" => ("icmp slt", "i1"),
|
pub(crate) fn builtin_binop_typed(name: &str, arg_ty: &Type) -> Option<(&'static str, &'static str)> {
|
||||||
"<=" => ("icmp sle", "i1"),
|
let is_int = matches!(arg_ty, Type::Con { name, .. } if name == "Int");
|
||||||
">" => ("icmp sgt", "i1"),
|
let is_float = matches!(arg_ty, Type::Con { name, .. } if name == "Float");
|
||||||
">=" => ("icmp sge", "i1"),
|
match (name, is_int, is_float) {
|
||||||
_ => return None,
|
("+", 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 {
|
pub(crate) fn c_byte_len(s: &str) -> usize {
|
||||||
|
|||||||
Reference in New Issue
Block a user