Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/dialect-mir/src/rust_intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
53 changes: 51 additions & 2 deletions crates/llvm-export/src/export/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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::<FastmathFlagsAttr>(&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)?;
Expand Down Expand Up @@ -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<pliron::r#type::TypeObj>, ctx: &pliron::context::Context) -> u32 {
ty.deref(ctx)
Expand Down
57 changes: 57 additions & 0 deletions crates/llvm-export/tests/export_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)),
}
}

Expand Down Expand Up @@ -259,6 +269,23 @@ impl RustFloatMathIntrinsic {
}
}

/// Recognize `core::intrinsics` / `std::intrinsics` generic fast-float ops.
fn from_fast_intrinsic_path(name: &str) -> Option<Self> {
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 {
Expand Down Expand Up @@ -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,
}
}
}
Expand Down
117 changes: 114 additions & 3 deletions crates/mir-lower/src/convert/ops/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -221,6 +222,11 @@ enum RustFloatMathIntrinsic {
AtanF64,
CbrtF32,
CbrtF64,
FaddFast,
FsubFast,
FmulFast,
FdivFast,
FremFast,
}

impl RustFloatMathIntrinsic {
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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"
)
}
}
}

Expand All @@ -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<FastFloatBinop> {
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 {
Expand Down Expand Up @@ -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())?;
Expand All @@ -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<Operation>,
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,
Expand Down
Loading
Loading