From 097274c82cb1c23172acafbb3e3b824b5bfa0140 Mon Sep 17 00:00:00 2001 From: nik Date: Fri, 12 Jun 2026 15:23:33 +0000 Subject: [PATCH 1/2] fix(intrinsics): lower fast float ops Signed-off-by: nik --- crates/dialect-mir/src/rust_intrinsics.rs | 15 ++ .../terminator/intrinsics/float_math.rs | 34 +++- crates/mir-lower/src/convert/ops/call.rs | 117 +++++++++++- crates/mir-lower/tests/lowering_test.rs | 179 ++++++++++++++++++ 4 files changed, 341 insertions(+), 4 deletions(-) diff --git a/crates/dialect-mir/src/rust_intrinsics.rs b/crates/dialect-mir/src/rust_intrinsics.rs index b125361c57..ccf9ee314c 100644 --- a/crates/dialect-mir/src/rust_intrinsics.rs +++ b/crates/dialect-mir/src/rust_intrinsics.rs @@ -152,3 +152,18 @@ pub const CALLEE_ATAN_F64: &str = placeholder!("atanf64"); pub const CALLEE_CBRT_F32: &str = placeholder!("cbrtf32"); /// Placeholder call used for `f64::cbrt` / `std::sys::cmath::cbrt`. pub const CALLEE_CBRT_F64: &str = placeholder!("cbrtf64"); + +/// Placeholder call used for `core::intrinsics::fadd_fast` (generic over float type). +/// +/// Lowered to `llvm.fadd` with explicit `fast` fast-math flags. The `f*_fast` intrinsics +/// assume finite, non-NaN inputs; LLVM's fast-math flags express the same +/// preconditions, so the binop replaces the call directly. +pub const CALLEE_FADD_FAST: &str = placeholder!("fadd_fast"); +/// Placeholder call used for `core::intrinsics::fsub_fast` (generic over float type). +pub const CALLEE_FSUB_FAST: &str = placeholder!("fsub_fast"); +/// Placeholder call used for `core::intrinsics::fmul_fast` (generic over float type). +pub const CALLEE_FMUL_FAST: &str = placeholder!("fmul_fast"); +/// Placeholder call used for `core::intrinsics::fdiv_fast` (generic over float type). +pub const CALLEE_FDIV_FAST: &str = placeholder!("fdiv_fast"); +/// Placeholder call used for `core::intrinsics::frem_fast` (generic over float type). +pub const CALLEE_FREM_FAST: &str = placeholder!("frem_fast"); diff --git a/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs b/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs index 7d9808e532..9277078f26 100644 --- a/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs +++ b/crates/mir-importer/src/translator/terminator/intrinsics/float_math.rs @@ -117,6 +117,16 @@ pub enum RustFloatMathIntrinsic { CbrtF32, /// `f64::cbrt` / `std::sys::cmath::cbrt`. CbrtF64, + /// Generic `core::intrinsics::fadd_fast` (lowered to `llvm.fadd` + fast-math). + FaddFast, + /// Generic `core::intrinsics::fsub_fast` (lowered to `llvm.fsub` + fast-math). + FsubFast, + /// Generic `core::intrinsics::fmul_fast` (lowered to `llvm.fmul` + fast-math). + FmulFast, + /// Generic `core::intrinsics::fdiv_fast` (lowered to `llvm.fdiv` + fast-math). + FdivFast, + /// Generic `core::intrinsics::frem_fast` (lowered to `llvm.frem` + fast-math). + FremFast, } impl RustFloatMathIntrinsic { @@ -190,7 +200,7 @@ impl RustFloatMathIntrinsic { "std::sys::cmath::cbrt" => Some(Self::CbrtF64), "core::num::imp::libm::cbrtf" => Some(Self::CbrtF32), "core::num::imp::libm::cbrt" => Some(Self::CbrtF64), - other => Self::from_libm_path(other), + other => Self::from_libm_path(other).or_else(|| Self::from_fast_intrinsic_path(other)), } } @@ -259,6 +269,23 @@ impl RustFloatMathIntrinsic { } } + /// Recognize `core::intrinsics` / `std::intrinsics` generic fast-float ops. + fn from_fast_intrinsic_path(name: &str) -> Option { + match name { + // Generic finite-input arithmetic intrinsics. The FQDN carries no + // float-type suffix because they're polymorphic over `T: + // FloatPrimitive`; the float type is in the call's substs and + // recovered from the destination's `body.locals()[…].ty` at + // lowering time. + "core::intrinsics::fadd_fast" | "std::intrinsics::fadd_fast" => Some(Self::FaddFast), + "core::intrinsics::fsub_fast" | "std::intrinsics::fsub_fast" => Some(Self::FsubFast), + "core::intrinsics::fmul_fast" | "std::intrinsics::fmul_fast" => Some(Self::FmulFast), + "core::intrinsics::fdiv_fast" | "std::intrinsics::fdiv_fast" => Some(Self::FdivFast), + "core::intrinsics::frem_fast" | "std::intrinsics::frem_fast" => Some(Self::FremFast), + _ => None, + } + } + /// Return the internal placeholder name used until MIR-to-LLVM lowering. pub fn placeholder_callee(self) -> &'static str { match self { @@ -311,6 +338,11 @@ impl RustFloatMathIntrinsic { Self::AtanF64 => rust_intrinsics::CALLEE_ATAN_F64, Self::CbrtF32 => rust_intrinsics::CALLEE_CBRT_F32, Self::CbrtF64 => rust_intrinsics::CALLEE_CBRT_F64, + Self::FaddFast => rust_intrinsics::CALLEE_FADD_FAST, + Self::FsubFast => rust_intrinsics::CALLEE_FSUB_FAST, + Self::FmulFast => rust_intrinsics::CALLEE_FMUL_FAST, + Self::FdivFast => rust_intrinsics::CALLEE_FDIV_FAST, + Self::FremFast => rust_intrinsics::CALLEE_FREM_FAST, } } } diff --git a/crates/mir-lower/src/convert/ops/call.rs b/crates/mir-lower/src/convert/ops/call.rs index 50903bf817..c17a8b28d4 100644 --- a/crates/mir-lower/src/convert/ops/call.rs +++ b/crates/mir-lower/src/convert/ops/call.rs @@ -68,9 +68,10 @@ use crate::helpers; use dialect_mir::ops::{MirCallOp, MirFuncOp}; use dialect_mir::rust_intrinsics; use dialect_mir::types::{MirDisjointSliceType, MirSliceType, MirStructType, MirTupleType}; -use llvm_export::attributes::IntegerOverflowFlagsAttr; +use llvm_export::attributes::{FastmathFlags, FastmathFlagsAttr, IntegerOverflowFlagsAttr}; use llvm_export::op_interfaces::{ - BinArithOp, CastOpInterface, CastOpWithNNegInterface, IntBinArithOpWithOverflowFlag, + BinArithOp, CastOpInterface, CastOpWithNNegInterface, FloatBinArithOpWithFastMathFlags, + IntBinArithOpWithOverflowFlag, }; use llvm_export::ops as llvm; use llvm_export::types as llvm_types; @@ -221,6 +222,11 @@ enum RustFloatMathIntrinsic { AtanF64, CbrtF32, CbrtF64, + FaddFast, + FsubFast, + FmulFast, + FdivFast, + FremFast, } impl RustFloatMathIntrinsic { @@ -276,6 +282,11 @@ impl RustFloatMathIntrinsic { rust_intrinsics::CALLEE_ATAN_F64 => Some(Self::AtanF64), rust_intrinsics::CALLEE_CBRT_F32 => Some(Self::CbrtF32), rust_intrinsics::CALLEE_CBRT_F64 => Some(Self::CbrtF64), + rust_intrinsics::CALLEE_FADD_FAST => Some(Self::FaddFast), + rust_intrinsics::CALLEE_FSUB_FAST => Some(Self::FsubFast), + rust_intrinsics::CALLEE_FMUL_FAST => Some(Self::FmulFast), + rust_intrinsics::CALLEE_FDIV_FAST => Some(Self::FdivFast), + rust_intrinsics::CALLEE_FREM_FAST => Some(Self::FremFast), _ => None, } } @@ -342,6 +353,17 @@ impl RustFloatMathIntrinsic { Self::AtanF64 => Ok("__nv_atan"), Self::CbrtF32 => Ok("__nv_cbrtf"), Self::CbrtF64 => Ok("__nv_cbrt"), + Self::FaddFast | Self::FsubFast | Self::FmulFast | Self::FdivFast | Self::FremFast => { + // The `f*_fast` intrinsics lower directly to LLVM `fadd`/`fsub`/ + // `fmul`/`fdiv`/`frem` with fast-math flags, not to a libdevice + // call. The caller (`convert_rust_float_math_intrinsic`) routes + // them through `lower_fast_binop` and never reaches this branch. + pliron::input_err!( + loc, + "f*_fast intrinsics have no libdevice equivalent; \ + they lower to llvm float binops with fast-math flags" + ) + } } } @@ -359,11 +381,38 @@ impl RustFloatMathIntrinsic { | Self::MinNumNszF32 | Self::MinNumNszF64 | Self::Atan2F32 - | Self::Atan2F64 => 2, + | Self::Atan2F64 + | Self::FaddFast + | Self::FsubFast + | Self::FmulFast + | Self::FdivFast + | Self::FremFast => 2, Self::FmaF32 | Self::FmaF64 | Self::FmuladdF32 | Self::FmuladdF64 => 3, _ => 1, } } + + /// Return the equivalent fast-math binop kind, if this intrinsic is one. + fn fast_binop(self) -> Option { + match self { + Self::FaddFast => Some(FastFloatBinop::Add), + Self::FsubFast => Some(FastFloatBinop::Sub), + Self::FmulFast => Some(FastFloatBinop::Mul), + Self::FdivFast => Some(FastFloatBinop::Div), + Self::FremFast => Some(FastFloatBinop::Rem), + _ => None, + } + } +} + +/// Which `llvm.f*` binary op a `core::intrinsics::f*_fast` intrinsic lowers to. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum FastFloatBinop { + Add, + Sub, + Mul, + Div, + Rem, } fn anyhow_to_pliron(e: anyhow::Error) -> pliron::result::Error { @@ -891,6 +940,14 @@ fn convert_rust_float_math_intrinsic( ); } + // `f*_fast` intrinsics lower to LLVM float binops with fast-math flags, + // not to a libdevice call. Route them off the libdevice path before any + // intrinsic-name lookup so polymorphic-over-T arithmetic on `f32` and + // `f64` both work without per-type intrinsic dispatch. + if let Some(binop) = intrinsic.fast_binop() { + return lower_fast_binop(ctx, rewriter, op, &args, binop, loc); + } + let result_mir_ty = op.deref(ctx).get_result(0).get_type(ctx); let result_ty = convert_type(ctx, result_mir_ty).map_err(anyhow_to_pliron)?; let intrinsic_name = intrinsic.libdevice_name(ctx, result_ty, loc.clone())?; @@ -915,6 +972,60 @@ fn convert_rust_float_math_intrinsic( Ok(()) } +/// Lower a `core::intrinsics::f*_fast` placeholder call to the matching +/// LLVM float binop with fast-math flags. +/// +/// The `f*_fast` family is generic over `T: FloatPrimitive`; rustc gives it +/// no MIR body, so the importer emitted a placeholder `mir.call` and any +/// downstream attempt to resolve the call as a symbol fails LLVM module +/// verification. Replacing the call with a binop here gives LLVM the explicit +/// `fast` fast-math flag set promised by the Rust intrinsic contract, so f32 +/// and f64 monomorphizations both work without per-type intrinsic dispatch. +fn fast_float_intrinsic_flags() -> FastmathFlagsAttr { + // pliron-llvm's `FastmathFlagsAttr::default()` is `FastmathFlags::empty()`; + // `core::intrinsics::f*_fast` needs the explicit LLVM `fast` flag group. + FastmathFlags::FAST.into() +} + +fn lower_fast_binop( + ctx: &mut Context, + rewriter: &mut DialectConversionRewriter, + op: Ptr, + args: &[Value], + binop: FastFloatBinop, + loc: pliron::location::Location, +) -> Result<()> { + let (lhs, rhs) = match args { + [a, b] => (*a, *b), + _ => { + return pliron::input_err!(loc, "f*_fast intrinsic call must have exactly 2 operands"); + } + }; + + let flags = fast_float_intrinsic_flags(); + let llvm_op = match binop { + FastFloatBinop::Add => { + llvm::FAddOp::new_with_fast_math_flags(ctx, lhs, rhs, flags).get_operation() + } + FastFloatBinop::Sub => { + llvm::FSubOp::new_with_fast_math_flags(ctx, lhs, rhs, flags).get_operation() + } + FastFloatBinop::Mul => { + llvm::FMulOp::new_with_fast_math_flags(ctx, lhs, rhs, flags).get_operation() + } + FastFloatBinop::Div => { + llvm::FDivOp::new_with_fast_math_flags(ctx, lhs, rhs, flags).get_operation() + } + FastFloatBinop::Rem => { + llvm::FRemOp::new_with_fast_math_flags(ctx, lhs, rhs, flags).get_operation() + } + }; + + rewriter.insert_operation(ctx, llvm_op); + rewriter.replace_operation(ctx, op, llvm_op); + Ok(()) +} + /// Read the width from an integer type, or report a useful lowering error. fn integer_bit_width( ctx: &Context, diff --git a/crates/mir-lower/tests/lowering_test.rs b/crates/mir-lower/tests/lowering_test.rs index 8d7d879ef5..bb919ec17e 100644 --- a/crates/mir-lower/tests/lowering_test.rs +++ b/crates/mir-lower/tests/lowering_test.rs @@ -823,6 +823,185 @@ fn append_return(ctx: &mut Context, block: pliron::context::Ptr Result<(), anyhow::Error> { + use dialect_mir::rust_intrinsics; + use llvm_export::attributes::{FastmathFlags, FastmathFlagsAttr}; + use llvm_export::op_interfaces::FastMathFlags; + use pliron::builtin::attributes::StringAttr; + use pliron::builtin::op_interfaces::CallOpInterface; + use pliron::builtin::types::{FP32Type, FP64Type}; + use pliron::r#type::{TypeObj, Typed}; + + let mut ctx = make_test_ctx(); + let f32_ty = FP32Type::get(&ctx); + let f64_ty = FP64Type::get(&ctx); + let f32_ty_obj: pliron::context::Ptr = f32_ty.into(); + let f64_ty_obj: pliron::context::Ptr = f64_ty.into(); + let (module_ptr, entry) = build_test_kernel( + &mut ctx, + vec![f32_ty_obj, f32_ty_obj, f64_ty_obj, f64_ty_obj], + ); + let f32_lhs = entry.deref(&ctx).get_argument(0); + let f32_rhs = entry.deref(&ctx).get_argument(1); + let f64_lhs = entry.deref(&ctx).get_argument(2); + let f64_rhs = entry.deref(&ctx).get_argument(3); + + for (callee, lhs, rhs, result_ty) in [ + ( + rust_intrinsics::CALLEE_FADD_FAST, + f32_lhs, + f32_rhs, + f32_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FSUB_FAST, + f32_lhs, + f32_rhs, + f32_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FMUL_FAST, + f32_lhs, + f32_rhs, + f32_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FDIV_FAST, + f32_lhs, + f32_rhs, + f32_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FREM_FAST, + f32_lhs, + f32_rhs, + f32_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FADD_FAST, + f64_lhs, + f64_rhs, + f64_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FSUB_FAST, + f64_lhs, + f64_rhs, + f64_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FMUL_FAST, + f64_lhs, + f64_rhs, + f64_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FDIV_FAST, + f64_lhs, + f64_rhs, + f64_ty_obj, + ), + ( + rust_intrinsics::CALLEE_FREM_FAST, + f64_lhs, + f64_rhs, + f64_ty_obj, + ), + ] { + let call_ptr = Operation::new( + &mut ctx, + mir::MirCallOp::get_concrete_op_info(), + vec![result_ty], + vec![lhs, rhs], + vec![], + 0, + ); + let call = mir::MirCallOp::new(call_ptr); + call.set_attr_callee(&ctx, StringAttr::new(callee.to_string())); + call_ptr.insert_at_back(entry, &ctx); + } + append_return(&mut ctx, entry); + + mir_lower::lower_mir_to_llvm(&mut ctx, module_ptr).map_err(|e| anyhow::anyhow!("{}", e))?; + + let explicit_fast_flags: FastmathFlagsAttr = FastmathFlags::FAST.into(); + assert_ne!( + explicit_fast_flags, + FastmathFlagsAttr::default(), + "FastmathFlagsAttr::default() is empty; f*_fast must use explicit fast flags" + ); + + let mut fadd_counts = [0usize; 2]; + let mut fsub_counts = [0usize; 2]; + let mut fmul_counts = [0usize; 2]; + let mut fdiv_counts = [0usize; 2]; + let mut frem_counts = [0usize; 2]; + + macro_rules! count_fast_binop { + ($body_op:expr, $op_ty:ty, $counts:ident, $name:literal) => { + if let Some(op) = Operation::get_op::<$op_ty>($body_op, &ctx) { + assert_eq!( + op.fast_math_flags(&ctx), + explicit_fast_flags, + concat!($name, " must carry explicit LLVM fast-math flags") + ); + let result_ty = op.get_operation().deref(&ctx).get_result(0).get_type(&ctx); + if result_ty == f32_ty_obj { + $counts[0] += 1; + } else if result_ty == f64_ty_obj { + $counts[1] += 1; + } else { + panic!(concat!($name, " lowered to an unexpected result type")); + } + } + }; + } + + let module_op = module_ptr.deref(&ctx); + let region = module_op.get_region(0); + let block = region.deref(&ctx).iter(&ctx).next().unwrap(); + for op in block.deref(&ctx).iter(&ctx) { + let Some(func_op) = Operation::get_op::(op, &ctx) else { + continue; + }; + if func_op.get_symbol_name(&ctx).to_string() != "kernel_func" { + continue; + } + let func_region = func_op.get_operation().deref(&ctx).get_region(0); + for func_block in func_region.deref(&ctx).iter(&ctx) { + for body_op in func_block.deref(&ctx).iter(&ctx) { + assert!( + Operation::get_op::(body_op, &ctx).is_none(), + "f*_fast placeholder mir.call must not survive MIR lowering" + ); + if let Some(call) = Operation::get_op::(body_op, &ctx) + && let CallOpCallable::Direct(sym) = call.callee(&ctx) + { + let callee = sym.to_string(); + assert!( + !callee.starts_with(rust_intrinsics::PLACEHOLDER_PREFIX), + "lowered LLVM must not call unresolved Rust intrinsic placeholder `{callee}`" + ); + } + count_fast_binop!(body_op, llvm::FAddOp, fadd_counts, "fadd_fast"); + count_fast_binop!(body_op, llvm::FSubOp, fsub_counts, "fsub_fast"); + count_fast_binop!(body_op, llvm::FMulOp, fmul_counts, "fmul_fast"); + count_fast_binop!(body_op, llvm::FDivOp, fdiv_counts, "fdiv_fast"); + count_fast_binop!(body_op, llvm::FRemOp, frem_counts, "frem_fast"); + } + } + } + + assert_eq!(fadd_counts, [1, 1], "fadd_fast must lower for f32 and f64"); + assert_eq!(fsub_counts, [1, 1], "fsub_fast must lower for f32 and f64"); + assert_eq!(fmul_counts, [1, 1], "fmul_fast must lower for f32 and f64"); + assert_eq!(fdiv_counts, [1, 1], "fdiv_fast must lower for f32 and f64"); + assert_eq!(frem_counts, [1, 1], "frem_fast must lower for f32 and f64"); + + Ok(()) +} + // --------------------------------------------------------------------------- // cvt.f16x2 intrinsic lowering test // --------------------------------------------------------------------------- From 71680acbc4cecfda933d3e3ef08058abb573103c Mon Sep 17 00:00:00 2001 From: nihalpasham Date: Tue, 16 Jun 2026 11:50:13 +0530 Subject: [PATCH 2/2] llvm-export: emit fast-math flags on float binops The fast-float intrinsic lowering set FastmathFlags::FAST on the llvm float binops, but the textual exporter never read the attribute, so the flags were dropped at .ll emission and the fast-math contract was inert end to end. Read the fast-math attribute in export_binop and print the matching LLVM keywords (the all-bits set as the 'fast' shorthand, any subset as individual keywords) between the opcode and the type. This is a no-op for integer binops (no attribute) and for float ops lowered from ordinary Rust arithmetic (empty flags), so existing IR is unchanged; it only fires for the f*_fast intrinsics. Verified end to end: a kernel calling core::intrinsics::f{add,sub,mul,div,rem}_fast on f32 and f64 now exports 'f.. fast float'/'f.. fast double', while vecadd's plain 'fadd float' is unchanged. Adds a focused export regression test. --- crates/llvm-export/src/export/ops.rs | 53 ++++++++++++++++++++++- crates/llvm-export/tests/export_test.rs | 57 +++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/crates/llvm-export/src/export/ops.rs b/crates/llvm-export/src/export/ops.rs index f161a5cd0b..c2726f4061 100644 --- a/crates/llvm-export/src/export/ops.rs +++ b/crates/llvm-export/src/export/ops.rs @@ -25,9 +25,10 @@ use pliron::{ use crate::{ attributes::{ - AtomicOrderingAttr, AtomicRmwKindAttr, FCmpPredicateAttr, FPHalfAttr, GepIndexAttr, - ICmpPredicateAttr, + AtomicOrderingAttr, AtomicRmwKindAttr, FCmpPredicateAttr, FPHalfAttr, FastmathFlags, + FastmathFlagsAttr, GepIndexAttr, ICmpPredicateAttr, }, + op_interfaces::ATTR_KEY_FAST_MATH_FLAGS, ops, types::{FuncType, VoidType}, }; @@ -1103,7 +1104,21 @@ impl<'a> ModuleExportState<'a> { let rhs = op_ref.get_operand(1); let res_name = value_names.get(&res).unwrap(); + // Float binops (fadd/fsub/fmul/fdiv/frem) may carry fast-math flags; + // they are emitted right after the opcode (e.g. `fadd fast float ...`). + // Integer binops never carry the attribute, and float ops lowered from + // ordinary Rust arithmetic carry empty flags, so this is a no-op for + // every existing op and only fires for the `f*_fast` intrinsics. + let fast_math = op_ref + .attributes + .get::(&ATTR_KEY_FAST_MATH_FLAGS) + .map(|attr| attr.0) + .unwrap_or_else(FastmathFlags::empty); + write!(output, " {res_name} = {op_name} ").unwrap(); + if !fast_math.is_empty() { + write!(output, "{} ", fastmath_keywords(fast_math)).unwrap(); + } self.export_type(lhs.get_type(self.ctx), output)?; write!(output, " ").unwrap(); self.export_value(lhs, value_names, output)?; @@ -1151,6 +1166,40 @@ impl<'a> ModuleExportState<'a> { } } +/// Render LLVM fast-math flag keywords for the given flag set. +/// +/// The all-bits set (`FastmathFlags::FAST`) prints as the `fast` shorthand; +/// any proper subset prints the individual keywords in LLVM's canonical order. +/// Callers only emit this when the set is non-empty. +fn fastmath_keywords(flags: FastmathFlags) -> String { + if flags.contains(FastmathFlags::FAST) { + return "fast".to_string(); + } + let mut parts = Vec::new(); + if flags.contains(FastmathFlags::NNAN) { + parts.push("nnan"); + } + if flags.contains(FastmathFlags::NINF) { + parts.push("ninf"); + } + if flags.contains(FastmathFlags::NSZ) { + parts.push("nsz"); + } + if flags.contains(FastmathFlags::ARCP) { + parts.push("arcp"); + } + if flags.contains(FastmathFlags::CONTRACT) { + parts.push("contract"); + } + if flags.contains(FastmathFlags::AFN) { + parts.push("afn"); + } + if flags.contains(FastmathFlags::REASSOC) { + parts.push("reassoc"); + } + parts.join(" ") +} + /// Return the address space of a pointer type, or 0 for non-pointer types. fn addrspace_of(ty: Ptr, ctx: &pliron::context::Context) -> u32 { ty.deref(ctx) diff --git a/crates/llvm-export/tests/export_test.rs b/crates/llvm-export/tests/export_test.rs index 9454ff6aef..ee22a4df48 100644 --- a/crates/llvm-export/tests/export_test.rs +++ b/crates/llvm-export/tests/export_test.rs @@ -914,3 +914,60 @@ fn assert_no_undefined_temporaries(ir: &str) { "IR references undefined SSA temporaries: {undefined:?}\nIR:\n{ir}" ); } + +/// A float binop carrying `FastmathFlags` must export with the matching LLVM +/// fast-math keyword (`fast` for the all-bits set), while a float binop with no +/// flags must export with none. Regression guard: the textual exporter +/// previously dropped fast-math flags entirely, making the `f*_fast` intrinsic +/// lowering inert end to end. +#[test] +fn export_emits_fast_math_flags_only_on_flagged_float_ops() { + use llvm_export::attributes::FastmathFlags; + use llvm_export::op_interfaces::{BinArithOp, FloatBinArithOpWithFastMathFlags}; + use llvm_export::ops::{FAddOp, FMulOp}; + use pliron::builtin::types::FP32Type; + + let mut ctx = Context::new(); + let module = ModuleOp::new(&mut ctx, "test_module".try_into().unwrap()); + let module_region = module.get_operation().deref(&ctx).get_region(0); + let module_block = module_region.deref(&ctx).iter(&ctx).next().unwrap(); + + let f32_ty = FP32Type::get(&ctx); + let void_ty = VoidType::get(&ctx); + let func_ty = FuncType::get( + &mut ctx, + void_ty.to_ptr(), + vec![f32_ty.into(), f32_ty.into()], + false, + ); + let func = FuncOp::new(&mut ctx, "fast_math".try_into().unwrap(), func_ty); + let entry = func.get_or_create_entry_block(&mut ctx); + let a = entry.deref(&ctx).get_argument(0); + let b = entry.deref(&ctx).get_argument(1); + + // fadd with the full fast-math set. + let fadd = FAddOp::new_with_fast_math_flags(&mut ctx, a, b, FastmathFlags::FAST.into()); + fadd.get_operation().insert_at_back(entry, &ctx); + // fmul with no flags: must stay flag-free. + let fmul = FMulOp::new(&mut ctx, a, b); + fmul.get_operation().insert_at_back(entry, &ctx); + ReturnOp::new(&mut ctx, None) + .get_operation() + .insert_at_back(entry, &ctx); + func.get_operation().insert_at_back(module_block, &ctx); + + let ir = export_module_to_string(&ctx, &module).expect("export succeeds"); + + assert!( + ir.contains("fadd fast float"), + "fast-math fadd must export the `fast` keyword:\n{ir}" + ); + let fmul_line = ir + .lines() + .find(|line| line.contains("fmul")) + .expect("exported fmul line"); + assert!( + !fmul_line.contains("fast"), + "a float binop with no fast-math flags must not gain them:\n{ir}" + ); +}