#![allow(clippy::float_cmp)]
mod arith;
mod convert;
pub mod host;
pub mod rmm;
pub mod nan_handling;
pub mod half;
use crate::isa::op::AluOp;
use self::nan_handling::unbox_f32;
use crate::isa::fp::{FpFlags, RoundingMode};
use arith::{execute_f32, execute_f64};
use convert::{
I32_MAX_P1_F64, I32_MIN_F64, I64_MAX_P1_F64, I64_MIN_F64, U32_MAX_P1_F64, U64_MAX_P1_F64,
round_to_integer,
};
use half::execute_f16;
use host::{clear_host_fp_flags, read_host_fp_flags, restore_host_round_mode, set_host_round_mode};
use nan_handling::{is_snan_f32, is_snan_f64};
use rmm::rmm_fixup;
pub fn execute_full(op: AluOp, a: u64, b: u64, c: u64, is32: bool) -> (u64, FpFlags) {
let is_arith = matches!(
op,
AluOp::FAdd
| AluOp::FSub
| AluOp::FMul
| AluOp::FDiv
| AluOp::FSqrt
| AluOp::FMAdd
| AluOp::FMSub
| AluOp::FNMAdd
| AluOp::FNMSub
);
if is_arith {
clear_host_fp_flags();
let result = std::hint::black_box(if is32 {
execute_f32(
op,
std::hint::black_box(a),
std::hint::black_box(b),
std::hint::black_box(c),
)
} else {
execute_f64(
op,
std::hint::black_box(a),
std::hint::black_box(b),
std::hint::black_box(c),
)
});
let flags = read_host_fp_flags();
return (result, flags);
}
let mut flags = FpFlags::NONE;
match op {
AluOp::FEq => {
if is32 {
if is_snan_f32(unbox_f32(a)) || is_snan_f32(unbox_f32(b)) {
flags = flags | FpFlags::NV;
}
} else if is_snan_f64(f64::from_bits(a)) || is_snan_f64(f64::from_bits(b)) {
flags = flags | FpFlags::NV;
}
}
AluOp::FLt | AluOp::FLe => {
if is32 {
if unbox_f32(a).is_nan() || unbox_f32(b).is_nan() {
flags = flags | FpFlags::NV;
}
} else if f64::from_bits(a).is_nan() || f64::from_bits(b).is_nan() {
flags = flags | FpFlags::NV;
}
}
AluOp::FMin | AluOp::FMax => {
if is32 {
if is_snan_f32(unbox_f32(a)) || is_snan_f32(unbox_f32(b)) {
flags = flags | FpFlags::NV;
}
} else if is_snan_f64(f64::from_bits(a)) || is_snan_f64(f64::from_bits(b)) {
flags = flags | FpFlags::NV;
}
}
AluOp::FCvtWS | AluOp::FCvtWUS | AluOp::FCvtLS | AluOp::FCvtLUS => {
clear_host_fp_flags();
let val = if is32 { unbox_f32(a) as f64 } else { f64::from_bits(a) };
if val.is_nan() || val.is_infinite() {
flags = flags | FpFlags::NV;
} else {
let rounded = val.trunc();
let inexact = val != rounded;
let overflow = match op {
AluOp::FCvtWS => !(I32_MIN_F64..I32_MAX_P1_F64).contains(&rounded),
AluOp::FCvtWUS => !(0.0..U32_MAX_P1_F64).contains(&rounded),
AluOp::FCvtLS => !(I64_MIN_F64..I64_MAX_P1_F64).contains(&rounded),
AluOp::FCvtLUS => rounded < 0.0,
_ => false,
};
if overflow {
flags = flags | FpFlags::NV;
} else if inexact {
flags = flags | FpFlags::NX;
}
}
}
_ => {
}
}
let result = if is32 { execute_f32(op, a, b, c) } else { execute_f64(op, a, b, c) };
(result, flags)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum IntTarget {
I32,
U32,
I64,
U64,
}
impl IntTarget {
const fn of(op: AluOp) -> Option<Self> {
match op {
AluOp::FCvtWS => Some(Self::I32),
AluOp::FCvtWUS => Some(Self::U32),
AluOp::FCvtLS => Some(Self::I64),
AluOp::FCvtLUS => Some(Self::U64),
_ => None,
}
}
const fn max(self) -> u64 {
match self {
Self::I32 => i32::MAX as i64 as u64,
Self::U32 => u32::MAX as i32 as i64 as u64,
Self::I64 => i64::MAX as u64,
Self::U64 => u64::MAX,
}
}
const fn min(self) -> u64 {
match self {
Self::I32 => i32::MIN as i64 as u64,
Self::U32 | Self::U64 => 0,
Self::I64 => i64::MIN as u64,
}
}
}
pub fn execute_full_rm(
op: AluOp,
a: u64,
b: u64,
c: u64,
is_f16: bool,
is32: bool,
rm: RoundingMode,
) -> (u64, FpFlags) {
if is_f16 {
return execute_f16(op, a, b, c, rm);
}
if let Some(target) = IntTarget::of(op) {
let val = if is32 { unbox_f32(a) as f64 } else { f64::from_bits(a) };
let mut flags = FpFlags::NONE;
if val.is_nan() {
flags = flags | FpFlags::NV;
return (target.max(), flags);
}
if val.is_infinite() {
flags = flags | FpFlags::NV;
let result = if val > 0.0 { target.max() } else { target.min() };
return (result, flags);
}
let rounded = round_to_integer(val, rm);
let inexact = val != rounded;
let (overflow, result) = match target {
IntTarget::I32 => {
if (I32_MIN_F64..I32_MAX_P1_F64).contains(&rounded) {
(false, rounded as i32 as i64 as u64)
} else {
(true, if rounded > 0.0 { target.max() } else { target.min() })
}
}
IntTarget::U32 => {
if (0.0..U32_MAX_P1_F64).contains(&rounded) {
(false, rounded as u32 as i32 as i64 as u64)
} else {
(true, if rounded > 0.0 { target.max() } else { target.min() })
}
}
IntTarget::I64 => {
if (I64_MIN_F64..I64_MAX_P1_F64).contains(&rounded) {
(false, rounded as i64 as u64)
} else {
(true, if rounded > 0.0 { target.max() } else { target.min() })
}
}
IntTarget::U64 => {
if rounded < 0.0 {
(true, target.min())
} else if rounded >= U64_MAX_P1_F64 {
(true, target.max())
} else {
(false, rounded as u64)
}
}
};
if overflow {
flags = flags | FpFlags::NV;
} else if inexact {
flags = flags | FpFlags::NX;
}
return (result, flags);
}
let is_rm_sensitive_arith = matches!(
op,
AluOp::FAdd
| AluOp::FSub
| AluOp::FMul
| AluOp::FDiv
| AluOp::FSqrt
| AluOp::FMAdd
| AluOp::FMSub
| AluOp::FNMAdd
| AluOp::FNMSub
);
if is_rm_sensitive_arith {
let saved = set_host_round_mode(rm);
clear_host_fp_flags();
let result = std::hint::black_box(if is32 {
execute_f32(
op,
std::hint::black_box(a),
std::hint::black_box(b),
std::hint::black_box(c),
)
} else {
execute_f64(
op,
std::hint::black_box(a),
std::hint::black_box(b),
std::hint::black_box(c),
)
});
let flags = read_host_fp_flags();
restore_host_round_mode(saved);
if rm == RoundingMode::Rmm && flags.contains(FpFlags::NX) {
let fixed = rmm_fixup(op, a, b, is32, result);
return (fixed, flags);
}
return (result, flags);
}
execute_full(op, a, b, c, is32)
}