Add f16/f128 comparison support

This commit is contained in:
beetrees 2025-05-23 15:51:51 +01:00
parent 27a9590d3d
commit 38d48dbe08
No known key found for this signature in database
GPG key ID: 8791BD754191EBD6
4 changed files with 162 additions and 5 deletions

View file

@ -28,6 +28,42 @@ pub(crate) fn f32_to_f16(fx: &mut FunctionCx<'_, '_, '_>, value: Value) -> Value
if ret_ty == types::I16 { fx.bcx.ins().bitcast(types::F16, MemFlags::new(), ret) } else { ret }
}
pub(crate) fn fcmp(fx: &mut FunctionCx<'_, '_, '_>, cc: FloatCC, lhs: Value, rhs: Value) -> Value {
let ty = fx.bcx.func.dfg.value_type(lhs);
match ty {
types::F32 | types::F64 => fx.bcx.ins().fcmp(cc, lhs, rhs),
types::F16 => {
let lhs = f16_to_f32(fx, lhs);
let rhs = f16_to_f32(fx, rhs);
fx.bcx.ins().fcmp(cc, lhs, rhs)
}
types::F128 => {
let (name, int_cc) = match cc {
FloatCC::Equal => ("__eqtf2", IntCC::Equal),
FloatCC::NotEqual => ("__netf2", IntCC::NotEqual),
FloatCC::LessThan => ("__lttf2", IntCC::SignedLessThan),
FloatCC::LessThanOrEqual => ("__letf2", IntCC::SignedLessThanOrEqual),
FloatCC::GreaterThan => ("__gttf2", IntCC::SignedGreaterThan),
FloatCC::GreaterThanOrEqual => ("__getf2", IntCC::SignedGreaterThanOrEqual),
_ => unreachable!("not currently used in rustc_codegen_cranelift: {cc:?}"),
};
let res = fx.lib_call(
name,
vec![AbiParam::new(types::F128), AbiParam::new(types::F128)],
// FIXME(rust-lang/compiler-builtins#919): This should be `I64` on non-AArch64
// architectures, but switching it before compiler-builtins is fixed causes test
// failures.
vec![AbiParam::new(types::I32)],
&[lhs, rhs],
)[0];
let zero = fx.bcx.ins().iconst(types::I32, 0);
let res = fx.bcx.ins().icmp(int_cc, res, zero);
res
}
_ => unreachable!("{ty:?}"),
}
}
pub(crate) fn codegen_f128_binop(
fx: &mut FunctionCx<'_, '_, '_>,
bin_op: BinOp,
@ -62,3 +98,21 @@ pub(crate) fn neg_f128(fx: &mut FunctionCx<'_, '_, '_>, value: Value) -> Value {
let bits = fx.bcx.ins().iconcat(low, high);
fx.bcx.ins().bitcast(types::F128, MemFlags::new(), bits)
}
pub(crate) fn fmin_f128(fx: &mut FunctionCx<'_, '_, '_>, a: Value, b: Value) -> Value {
fx.lib_call(
"fminimumf128",
vec![AbiParam::new(types::F128), AbiParam::new(types::F128)],
vec![AbiParam::new(types::F128)],
&[a, b],
)[0]
}
pub(crate) fn fmax_f128(fx: &mut FunctionCx<'_, '_, '_>, a: Value, b: Value) -> Value {
fx.lib_call(
"fmaximumf128",
vec![AbiParam::new(types::F128), AbiParam::new(types::F128)],
vec![AbiParam::new(types::F128)],
&[a, b],
)[0]
}

View file

@ -67,6 +67,15 @@ builtin_functions! {
fn fmodf(a: f32, b: f32) -> f32;
fn fmod(a: f64, b: f64) -> f64;
fn fmodf128(a: f128, b: f128) -> f128;
// float comparison
fn __eqtf2(a: f128, b: f128) -> i32;
fn __netf2(a: f128, b: f128) -> i32;
fn __lttf2(a: f128, b: f128) -> i32;
fn __letf2(a: f128, b: f128) -> i32;
fn __gttf2(a: f128, b: f128) -> i32;
fn __getf2(a: f128, b: f128) -> i32;
fn fminimumf128(a: f128, b: f128) -> f128;
fn fmaximumf128(a: f128, b: f128) -> f128;
// Cranelift float libcalls
fn fmaf(a: f32, b: f32, c: f32) -> f32;
fn fma(a: f64, b: f64, c: f64) -> f64;

View file

@ -27,6 +27,7 @@ use rustc_span::{Symbol, sym};
pub(crate) use self::llvm::codegen_llvm_intrinsic_call;
use crate::cast::clif_intcast;
use crate::codegen_f16_f128;
use crate::prelude::*;
fn bug_on_incorrect_arg_count(intrinsic: impl std::fmt::Display) -> ! {
@ -1118,6 +1119,20 @@ fn codegen_regular_intrinsic_call<'tcx>(
ret.write_cvalue(fx, old);
}
sym::minimumf16 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
// FIXME(bytecodealliance/wasmtime#8312): Use `fmin` directly once
// Cranelift backend lowerings are implemented.
let a = codegen_f16_f128::f16_to_f32(fx, a);
let b = codegen_f16_f128::f16_to_f32(fx, b);
let val = fx.bcx.ins().fmin(a, b);
let val = codegen_f16_f128::f32_to_f16(fx, val);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16));
ret.write_cvalue(fx, val);
}
sym::minimumf32 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
@ -1136,6 +1151,31 @@ fn codegen_regular_intrinsic_call<'tcx>(
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64));
ret.write_cvalue(fx, val);
}
sym::minimumf128 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
// FIXME(bytecodealliance/wasmtime#8312): Use `fmin` once Cranelift
// backend lowerings are implemented.
let val = codegen_f16_f128::fmin_f128(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f128));
ret.write_cvalue(fx, val);
}
sym::maximumf16 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
// FIXME(bytecodealliance/wasmtime#8312): Use `fmax` directly once
// Cranelift backend lowerings are implemented.
let a = codegen_f16_f128::f16_to_f32(fx, a);
let b = codegen_f16_f128::f16_to_f32(fx, b);
let val = fx.bcx.ins().fmax(a, b);
let val = codegen_f16_f128::f32_to_f16(fx, val);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16));
ret.write_cvalue(fx, val);
}
sym::maximumf32 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
@ -1154,7 +1194,27 @@ fn codegen_regular_intrinsic_call<'tcx>(
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64));
ret.write_cvalue(fx, val);
}
sym::maximumf128 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
// FIXME(bytecodealliance/wasmtime#8312): Use `fmax` once Cranelift
// backend lowerings are implemented.
let val = codegen_f16_f128::fmax_f128(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f128));
ret.write_cvalue(fx, val);
}
sym::minnumf16 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
let val = crate::num::codegen_float_min(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16));
ret.write_cvalue(fx, val);
}
sym::minnumf32 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
@ -1173,6 +1233,24 @@ fn codegen_regular_intrinsic_call<'tcx>(
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64));
ret.write_cvalue(fx, val);
}
sym::minnumf128 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
let val = crate::num::codegen_float_min(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f128));
ret.write_cvalue(fx, val);
}
sym::maxnumf16 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
let val = crate::num::codegen_float_max(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f16));
ret.write_cvalue(fx, val);
}
sym::maxnumf32 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
@ -1191,6 +1269,15 @@ fn codegen_regular_intrinsic_call<'tcx>(
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f64));
ret.write_cvalue(fx, val);
}
sym::maxnumf128 => {
intrinsic_args!(fx, args => (a, b); intrinsic);
let a = a.load_scalar(fx);
let b = b.load_scalar(fx);
let val = crate::num::codegen_float_max(fx, a, b);
let val = CValue::by_val(val, fx.layout_of(fx.tcx.types.f128));
ret.write_cvalue(fx, val);
}
sym::catch_unwind => {
intrinsic_args!(fx, args => (f, data, catch_fn); intrinsic);

View file

@ -416,7 +416,10 @@ pub(crate) fn codegen_float_binop<'tcx>(
BinOp::Gt => FloatCC::GreaterThan,
_ => unreachable!(),
};
let val = fx.bcx.ins().fcmp(fltcc, lhs, rhs);
// FIXME(bytecodealliance/wasmtime#8312): Replace with Cranelift
// `fcmp` once `f16`/`f128` backend lowerings have been added to
// Cranelift.
let val = codegen_f16_f128::fcmp(fx, fltcc, lhs, rhs);
return CValue::by_val(val, fx.layout_of(fx.tcx.types.bool));
}
_ => unreachable!("{:?}({:?}, {:?})", bin_op, in_lhs, in_rhs),
@ -500,15 +503,19 @@ fn codegen_ptr_binop<'tcx>(
// and `a.is_nan() ? b : (a <= b ? b : a)` for `maxnumf*`. NaN checks are done by comparing
// a float against itself. Only in case of NaN is it not equal to itself.
pub(crate) fn codegen_float_min(fx: &mut FunctionCx<'_, '_, '_>, a: Value, b: Value) -> Value {
let a_is_nan = fx.bcx.ins().fcmp(FloatCC::NotEqual, a, a);
let a_ge_b = fx.bcx.ins().fcmp(FloatCC::GreaterThanOrEqual, a, b);
// FIXME(bytecodealliance/wasmtime#8312): Replace with Cranelift `fcmp` once
// `f16`/`f128` backend lowerings have been added to Cranelift.
let a_is_nan = codegen_f16_f128::fcmp(fx, FloatCC::NotEqual, a, a);
let a_ge_b = codegen_f16_f128::fcmp(fx, FloatCC::GreaterThanOrEqual, a, b);
let temp = fx.bcx.ins().select(a_ge_b, b, a);
fx.bcx.ins().select(a_is_nan, b, temp)
}
pub(crate) fn codegen_float_max(fx: &mut FunctionCx<'_, '_, '_>, a: Value, b: Value) -> Value {
let a_is_nan = fx.bcx.ins().fcmp(FloatCC::NotEqual, a, a);
let a_le_b = fx.bcx.ins().fcmp(FloatCC::LessThanOrEqual, a, b);
// FIXME(bytecodealliance/wasmtime#8312): Replace with Cranelift `fcmp` once
// `f16`/`f128` backend lowerings have been added to Cranelift.
let a_is_nan = codegen_f16_f128::fcmp(fx, FloatCC::NotEqual, a, a);
let a_le_b = codegen_f16_f128::fcmp(fx, FloatCC::LessThanOrEqual, a, b);
let temp = fx.bcx.ins().select(a_le_b, b, a);
fx.bcx.ins().select(a_is_nan, b, temp)
}