use inkwell::FloatPredicate;
use inkwell::module::Linkage;
use inkwell::values::{FloatValue, IntValue};
use sv_lang::{BinaryOp, KnownSystemName};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue};
pub const SV_RT_REAL_MATH_SYMBOL: &str = "sv_rt_real_math";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum MathOp {
Ln = 0,
Log10 = 1,
Exp = 2,
Sqrt = 3,
Pow = 4,
Floor = 5,
Ceil = 6,
Sin = 7,
Cos = 8,
Tan = 9,
Asin = 10,
Acos = 11,
Atan = 12,
Atan2 = 13,
Hypot = 14,
Sinh = 15,
Cosh = 16,
Tanh = 17,
Asinh = 18,
Acosh = 19,
Atanh = 20,
}
impl MathOp {
pub fn eval(op: u32, a: f64, b: f64) -> f64 {
vj_runtime::real_math::eval(op, a, b)
}
}
pub(crate) fn bits_to_real<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
) -> RealValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let bits = op.lane_val(pv);
let llvm = if pv.width == 32 {
let f =
cx.b.build_bit_cast(bits, cx.ctx.f32_type(), "shortreal_bits")
.unwrap()
.into_float_value();
cx.b.build_float_ext(f, cx.ctx.f64_type(), "shortreal_ext")
.unwrap()
} else {
cx.b.build_bit_cast(bits, cx.ctx.f64_type(), "real_bits")
.unwrap()
.into_float_value()
};
RealValue { llvm }
}
pub(crate) fn real_to_bits<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rv: RealValue<'ctx>,
width: u32,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let f = as_f64(cx, rv.llvm);
let bits = if width == 32 {
let t =
cx.b.build_float_trunc(f, cx.ctx.f32_type(), "to_shortreal")
.unwrap();
cx.b.build_bit_cast(t, cx.ctx.i32_type(), "shortreal_to_bits")
.unwrap()
.into_int_value()
} else {
cx.b.build_bit_cast(f, cx.ctx.i64_type(), "real_to_bits")
.unwrap()
.into_int_value()
};
op.pack(bits, op.i_n(width).const_zero(), width, cx.repr, false)
}
pub(crate) fn as_f64<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, f: FloatValue<'ctx>) -> FloatValue<'ctx> {
if f.get_type() == cx.ctx.f32_type() {
cx.b.build_float_ext(f, cx.ctx.f64_type(), "to_f64")
.unwrap()
} else {
f
}
}
pub(crate) fn round_to_short<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
f: FloatValue<'ctx>,
) -> FloatValue<'ctx> {
let t =
cx.b.build_float_trunc(as_f64(cx, f), cx.ctx.f32_type(), "round_short")
.unwrap();
cx.b.build_float_ext(t, cx.ctx.f64_type(), "round_short_ext")
.unwrap()
}
pub(crate) fn to_storage<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: LoweredValue<'ctx>,
width: u32,
what: &str,
) -> Result<PackedValue<'ctx>, LoweringError> {
match v {
LoweredValue::Real(rv) => Ok(real_to_bits(cx, rv, width)),
LoweredValue::Str(s) => Ok(crate::strings::as_packed(cx, s)),
other => other.into_packed(what, &cx.fn_sym),
}
}
pub(crate) fn real_truth<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, rv: RealValue<'ctx>) -> IntValue<'ctx> {
let f = as_f64(cx, rv.llvm);
cx.b.build_float_compare(
FloatPredicate::UNE,
f,
cx.ctx.f64_type().const_zero(),
"real_truth",
)
.unwrap()
}
pub(crate) fn real_truth_packed<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rv: RealValue<'ctx>,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
op.pack(
real_truth(cx, rv),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)
}
pub(crate) fn real_to_count<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rv: RealValue<'ctx>,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let f = as_f64(cx, rv.llvm);
let nan =
cx.b.build_float_compare(FloatPredicate::UNO, f, f, "count_nan")
.unwrap();
let safe =
cx.b.build_select(nan, cx.ctx.f64_type().const_zero(), f, "count_safe")
.unwrap()
.into_float_value();
let n = op.round_real_to_int(RealValue { llvm: safe }, 64, true);
op.pack(n, op.i_n(64).const_zero(), 64, cx.repr, true)
}
pub(crate) fn packed_to_real<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
) -> Option<RealValue<'ctx>> {
if pv.width > 64 {
return None;
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let raw = op.lane_val(pv);
let known = cx.b.build_not(op.lane_unk(pv), "r_known").unwrap();
let raw = cx.b.build_and(raw, known, "r_masked").unwrap();
let f64t = cx.ctx.f64_type();
let llvm = if pv.signed {
cx.b.build_signed_int_to_float(raw, f64t, "case_itof")
} else {
cx.b.build_unsigned_int_to_float(raw, f64t, "case_utof")
}
.unwrap();
Some(RealValue { llvm })
}
fn bool_result<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, bit: IntValue<'ctx>) -> LoweredValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(op.pack(bit, cx.ctx.bool_type().const_zero(), 1, cx.repr, false))
}
pub(crate) fn lower_real_inside<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: RealValue<'ctx>,
candidates: Vec<sv_lang::Expression<'d>>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_lang::kinds::ExpressionKind;
let x = as_f64(cx, left.llvm);
let f64t = cx.ctx.f64_type();
let real_of = |e: Option<sv_lang::Expression<'d>>| -> Result<FloatValue<'ctx>, LoweringError> {
let e = e.ok_or_else(|| {
LoweringError::unsupported_at("inside range missing a bound", &cx.fn_sym)
})?;
match lower_expr(cx, e)? {
LoweredValue::Real(r) => Ok(as_f64(cx, r.llvm)),
LoweredValue::Packed(p) => packed_to_real(cx, p).map(|r| r.llvm).ok_or_else(|| {
LoweringError::unsupported_at(
"inside with a real left operand and a vector wider than 64 bits",
&cx.fn_sym,
)
}),
_ => Err(LoweringError::unsupported_at(
"inside with a real left operand and a non-numeric item",
&cx.fn_sym,
)),
}
};
let open = |e: Option<sv_lang::Expression<'d>>| {
let mut e = e;
while let Some(c) = e.filter(|c| c.kind() == ExpressionKind::Conversion) {
e = c.conversion_operand();
}
e.is_some_and(|e| {
e.kind() == ExpressionKind::UnboundedLiteral
|| matches!(e.constant_value(), Some(sv_lang::ConstantValue::Unbounded))
})
};
let i1 = cx.ctx.bool_type();
let mut acc: Option<IntValue<'ctx>> = None;
for cand in candidates {
let hit = if cand.kind() == ExpressionKind::ValueRange {
let (lo, hi) = if let Some(relative) = crate::expr::tolerance_op(cand) {
let a = real_of(cand.left())?;
let b = real_of(cand.right())?;
let t = if relative {
let p = cx.b.build_float_mul(a, b, "tolp").unwrap();
cx.b.build_float_div(p, f64t.const_float(100.0), "tolpct")
.unwrap()
} else {
b
};
let l = cx.b.build_float_sub(a, t, "tollo").unwrap();
let h = cx.b.build_float_add(a, t, "tolhi").unwrap();
let swap =
cx.b.build_float_compare(FloatPredicate::OGT, l, h, "tolswap")
.unwrap();
let lo = cx.b.build_select(swap, h, l, "tolmin").unwrap();
let hi = cx.b.build_select(swap, l, h, "tolmax").unwrap();
(Some(lo.into_float_value()), Some(hi.into_float_value()))
} else {
let lo = if open(cand.left()) {
None
} else {
Some(real_of(cand.left())?)
};
let hi = if open(cand.right()) {
None
} else {
Some(real_of(cand.right())?)
};
(lo, hi)
};
let ge = match lo {
Some(lo) => {
cx.b.build_float_compare(FloatPredicate::OGE, x, lo, "inge")
.unwrap()
}
None => i1.const_int(1, false),
};
let le = match hi {
Some(hi) => {
cx.b.build_float_compare(FloatPredicate::OLE, x, hi, "inle")
.unwrap()
}
None => i1.const_int(1, false),
};
cx.b.build_and(ge, le, "inrange").unwrap()
} else {
if cand
.expr_type()
.is_some_and(|t| t.is_unpacked_array() || t.is_queue() || t.is_associative_array())
{
real_inside_array(cx, x, cand)?
} else {
let v = real_of(Some(cand))?;
cx.b.build_float_compare(FloatPredicate::OEQ, x, v, "ineq")
.unwrap()
}
};
acc = Some(match acc {
None => hit,
Some(prev) => cx.b.build_or(prev, hit, "inor").unwrap(),
});
}
let acc = acc.ok_or_else(|| {
LoweringError::unsupported_at("inside with an empty candidate list", &cx.fn_sym)
})?;
Ok(bool_result(cx, acc))
}
fn elem_as_real<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
item: LoweredValue<'ctx>,
) -> Result<FloatValue<'ctx>, LoweringError> {
match item {
LoweredValue::Real(r) => Ok(as_f64(cx, r.llvm)),
LoweredValue::Packed(p) => packed_to_real(cx, p).map(|r| r.llvm).ok_or_else(|| {
LoweringError::unsupported_at(
"inside with a real left operand over an array of elements wider than 64 bits",
&cx.fn_sym,
)
}),
_ => Err(LoweringError::unsupported_at(
"inside with a real left operand over an array of non-numeric elements",
&cx.fn_sym,
)),
}
}
fn real_inside_array<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
x: FloatValue<'ctx>,
cand: sv_lang::Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let i1 = cx.ctx.bool_type();
let i64t = cx.ctx.i64_type();
let eq = |v: FloatValue<'ctx>| {
cx.b.build_float_compare(FloatPredicate::OEQ, x, v, "inaeq")
.unwrap()
};
if let Some(c) = crate::collections::coll_of_expr(&cand) {
let h = crate::collections::handle_of(cx, cand)?;
let n = crate::collections::size_of_handle(cx, &c, h);
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let acc_slot = crate::expr::entry_alloca(cx, i1, "rinside_acc");
cx.b.build_store(acc_slot, i1.const_zero()).unwrap();
let i_slot = crate::expr::entry_alloca(cx, i64t, "rinside_i");
cx.b.build_store(i_slot, i64t.const_zero()).unwrap();
let head = cx.ctx.append_basic_block(f, "rinside_head");
let body = cx.ctx.append_basic_block(f, "rinside_body");
let done = cx.ctx.append_basic_block(f, "rinside_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let i = cx.b.build_load(i64t, i_slot, "i").unwrap().into_int_value();
let more =
cx.b.build_int_compare(inkwell::IntPredicate::SLT, i, n, "more")
.unwrap();
cx.b.build_conditional_branch(more, body, done).unwrap();
cx.b.position_at_end(body);
let item = crate::collections::read_pos(cx, &c, h, i);
let hit = eq(elem_as_real(cx, item)?);
let prev =
cx.b.build_load(i1, acc_slot, "acc")
.unwrap()
.into_int_value();
let next = cx.b.build_or(prev, hit, "rinside_or").unwrap();
cx.b.build_store(acc_slot, next).unwrap();
let inc =
cx.b.build_int_add(i, i64t.const_int(1, false), "i_next")
.unwrap();
cx.b.build_store(i_slot, inc).unwrap();
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
return Ok(cx
.b
.build_load(i1, acc_slot, "rinside_res")
.unwrap()
.into_int_value());
}
let target = match crate::arrays::array_target(cx, cand)? {
Some(t) => Some(t),
None => match crate::arrays::spill_member_array(cx, cand)? {
Some(t) => Some(t),
None => crate::arrays::array_call_value(cx, cand)?,
},
};
let Some((r, shape)) = target else {
return Err(LoweringError::unsupported_at(
"inside over an unpacked array that is not a variable, memory, member or call",
&cx.fn_sym,
));
};
let mut acc: Option<IntValue<'ctx>> = None;
for k in 0..shape.rows() {
let el = crate::arrays::ElemRef {
backing: r.backing,
row: cx
.b
.build_int_add(r.base, i64t.const_int(k, false), "rinside_row")
.unwrap(),
valid: r.valid,
width: shape.elem.value_width(),
signed: shape.elem.is_signed(),
default: crate::arrays::default_value(cx, &shape.elem),
};
let item = crate::arrays::read_element(cx, &el)?;
let item = if shape.elem.is_floating() {
LoweredValue::Real(bits_to_real(cx, item))
} else {
LoweredValue::Packed(item)
};
let hit = eq(elem_as_real(cx, item)?);
acc = Some(match acc {
None => hit,
Some(prev) => cx.b.build_or(prev, hit, "rinside_or").unwrap(),
});
}
acc.ok_or_else(|| LoweringError::unsupported_at("inside over a zero-element array", &cx.fn_sym))
}
pub(crate) fn real_binary<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
bop: BinaryOp,
l: RealValue<'ctx>,
r: RealValue<'ctx>,
) -> Option<LoweredValue<'ctx>> {
let (a, b) = (as_f64(cx, l.llvm), as_f64(cx, r.llvm));
let real = |f: FloatValue<'ctx>| Some(LoweredValue::Real(RealValue { llvm: f }));
let cmp = |p: FloatPredicate, name: &str| {
Some(bool_result(
cx,
cx.b.build_float_compare(p, a, b, name).unwrap(),
))
};
match bop {
BinaryOp::Add => real(cx.b.build_float_add(a, b, "fadd").unwrap()),
BinaryOp::Subtract => real(cx.b.build_float_sub(a, b, "fsub").unwrap()),
BinaryOp::Multiply => real(cx.b.build_float_mul(a, b, "fmul").unwrap()),
BinaryOp::Divide => real(cx.b.build_float_div(a, b, "fdiv").unwrap()),
BinaryOp::Power => real(math_call(cx, MathOp::Pow, a, b)),
BinaryOp::Equality => cmp(FloatPredicate::OEQ, "feq"),
BinaryOp::Inequality => cmp(FloatPredicate::UNE, "fne"),
BinaryOp::LessThan => cmp(FloatPredicate::OLT, "flt"),
BinaryOp::LessThanEqual => cmp(FloatPredicate::OLE, "fle"),
BinaryOp::GreaterThan => cmp(FloatPredicate::OGT, "fgt"),
BinaryOp::GreaterThanEqual => cmp(FloatPredicate::OGE, "fge"),
BinaryOp::LogicalAnd => {
let t =
cx.b.build_and(real_truth(cx, l), real_truth(cx, r), "fland")
.unwrap();
Some(bool_result(cx, t))
}
BinaryOp::LogicalOr => {
let t =
cx.b.build_or(real_truth(cx, l), real_truth(cx, r), "flor")
.unwrap();
Some(bool_result(cx, t))
}
_ => None,
}
}
fn math_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: MathOp,
a: FloatValue<'ctx>,
b: FloatValue<'ctx>,
) -> FloatValue<'ctx> {
let f64_ty = cx.ctx.f64_type();
let f = cx
.module
.get_function(SV_RT_REAL_MATH_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_REAL_MATH_SYMBOL,
f64_ty.fn_type(
&[cx.ctx.i32_type().into(), f64_ty.into(), f64_ty.into()],
false,
),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[
cx.ctx.i32_type().const_int(op as u64, false).into(),
a.into(),
b.into(),
],
"real_math",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_float_value()
}
pub(crate) fn lower_real_sysfunc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
use KnownSystemName as K;
let math = match name {
K::Ln => Some(MathOp::Ln),
K::Log10 => Some(MathOp::Log10),
K::Exp => Some(MathOp::Exp),
K::Sqrt => Some(MathOp::Sqrt),
K::Pow => Some(MathOp::Pow),
K::Floor => Some(MathOp::Floor),
K::Ceil => Some(MathOp::Ceil),
K::Sin => Some(MathOp::Sin),
K::Cos => Some(MathOp::Cos),
K::Tan => Some(MathOp::Tan),
K::Asin => Some(MathOp::Asin),
K::Acos => Some(MathOp::Acos),
K::Atan => Some(MathOp::Atan),
K::Atan2 => Some(MathOp::Atan2),
K::Hypot => Some(MathOp::Hypot),
K::Sinh => Some(MathOp::Sinh),
K::Cosh => Some(MathOp::Cosh),
K::Tanh => Some(MathOp::Tanh),
K::Asinh => Some(MathOp::Asinh),
K::Acosh => Some(MathOp::Acosh),
K::Atanh => Some(MathOp::Atanh),
_ => None,
};
let known = math.is_some()
|| matches!(
name,
K::Itor
| K::Rtoi
| K::RealToBits
| K::BitsToReal
| K::ShortrealToBits
| K::BitsToShortreal
);
if !known {
return None;
}
Some(lower_known_real_sysfunc(cx, name, math, e))
}
fn lower_known_real_sysfunc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
math: Option<MathOp>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use KnownSystemName as K;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let args = e.operands();
let arg = |i: usize| -> Result<LoweredValue<'ctx>, LoweringError> {
let a = args.get(i).ok_or_else(|| {
LoweringError::unsupported_at("real system function missing an argument", &cx.fn_sym)
})?;
lower_expr(cx, *a)
};
let to_f64 = |v: LoweredValue<'ctx>| -> Result<FloatValue<'ctx>, LoweringError> {
Ok(match v {
LoweredValue::Real(r) => as_f64(cx, r.llvm),
LoweredValue::Packed(p) => {
let flat = op.to_2state4(p);
let raw = op.lane_val(flat);
if p.signed {
cx.b.build_signed_int_to_float(raw, cx.ctx.f64_type(), "itof")
.unwrap()
} else {
cx.b.build_unsigned_int_to_float(raw, cx.ctx.f64_type(), "utof")
.unwrap()
}
}
LoweredValue::Wide(_) | LoweredValue::Str(_) | LoweredValue::Huge(_) => {
return Err(LoweringError::unsupported_at(
"a >128-bit or string argument to a real system function",
&cx.fn_sym,
));
}
})
};
let real_arg = |i: usize| -> Result<FloatValue<'ctx>, LoweringError> { to_f64(arg(i)?) };
let ret_int = |bits: IntValue<'ctx>, width: u32, signed: bool| {
LoweredValue::Packed(op.pack(bits, op.i_n(width).const_zero(), width, cx.repr, signed))
};
if let Some(m) = math {
let a = real_arg(0)?;
let b = if matches!(m, MathOp::Pow | MathOp::Atan2 | MathOp::Hypot) {
real_arg(1)?
} else {
cx.ctx.f64_type().const_zero()
};
return Ok(LoweredValue::Real(RealValue {
llvm: math_call(cx, m, a, b),
}));
}
match name {
K::Itor => {
let a = arg(0)?;
if let LoweredValue::Real(r) = &a {
let f = as_f64(cx, r.llvm);
let diff = cx.b.build_float_sub(f, f, "itor_fin").unwrap();
let finite =
cx.b.build_float_compare(
FloatPredicate::OEQ,
diff,
cx.ctx.f64_type().const_zero(),
"itor_isfin",
)
.unwrap();
let safe =
cx.b.build_select(finite, f, cx.ctx.f64_type().const_zero(), "itor_safe")
.unwrap()
.into_float_value();
let n = op.round_real_to_int(RealValue { llvm: safe }, 64, true);
let back =
cx.b.build_signed_int_to_float(n, cx.ctx.f64_type(), "itor_back")
.unwrap();
return Ok(LoweredValue::Real(RealValue { llvm: back }));
}
Ok(LoweredValue::Real(RealValue { llvm: to_f64(a)? }))
}
K::Rtoi => {
let f = real_arg(0)?;
let i =
cx.b.build_float_to_signed_int(f, cx.ctx.i32_type(), "rtoi")
.unwrap();
Ok(ret_int(i, 32, true))
}
K::RealToBits => {
let f = real_arg(0)?;
let bits =
cx.b.build_bit_cast(f, cx.ctx.i64_type(), "realtobits")
.unwrap()
.into_int_value();
Ok(ret_int(bits, 64, false))
}
K::ShortrealToBits => {
let f = real_arg(0)?;
let t =
cx.b.build_float_trunc(f, cx.ctx.f32_type(), "shortreal")
.unwrap();
let bits =
cx.b.build_bit_cast(t, cx.ctx.i32_type(), "shortrealtobits")
.unwrap()
.into_int_value();
Ok(ret_int(bits, 32, false))
}
K::BitsToReal | K::BitsToShortreal => {
let width = if name == K::BitsToReal { 64 } else { 32 };
let p = arg(0)?.into_packed("$bitstoreal of a real value", &cx.fn_sym)?;
let flat = op.to_2state4(p);
let raw = op.lane_val(flat);
let fitted = if p.width == width {
raw
} else if p.width > width {
cx.b.build_int_truncate(raw, op.i_n(width), "bits_fit")
.unwrap()
} else {
cx.b.build_int_z_extend(raw, op.i_n(width), "bits_fit")
.unwrap()
};
let packed = op.pack(fitted, op.i_n(width).const_zero(), width, cx.repr, false);
Ok(LoweredValue::Real(bits_to_real(cx, packed)))
}
_ => unreachable!("lower_real_sysfunc only forwards known names"),
}
}
#[cfg(test)]
mod tests {
use super::MathOp;
#[test]
fn math_ops_evaluate_by_code() {
assert_eq!(MathOp::eval(MathOp::Sqrt as u32, 9.0, 0.0), 3.0);
assert_eq!(MathOp::eval(MathOp::Pow as u32, 2.0, 10.0), 1024.0);
assert_eq!(MathOp::eval(MathOp::Floor as u32, -1.5, 0.0), -2.0);
assert_eq!(MathOp::eval(MathOp::Hypot as u32, 3.0, 4.0), 5.0);
assert!(MathOp::eval(999, 1.0, 1.0).is_nan());
}
}