//! Architecture-specific support for aarch64 with neon.

use core::arch::asm;

pub fn fma(mut x: f64, y: f64, z: f64) -> f64 {
    // SAFETY: `fmadd` is available with neon and has no side effects.
    unsafe {
        asm!(
            "fmadd {x:d}, {x:d}, {y:d}, {z:d}",
            x = inout(vreg) x,
            y = in(vreg) y,
            z = in(vreg) z,
            options(nomem, nostack, pure)
        );
    }
    x
}

pub fn fmaf(mut x: f32, y: f32, z: f32) -> f32 {
    // SAFETY: `fmadd` is available with neon and has no side effects.
    unsafe {
        asm!(
            "fmadd {x:s}, {x:s}, {y:s}, {z:s}",
            x = inout(vreg) x,
            y = in(vreg) y,
            z = in(vreg) z,
            options(nomem, nostack, pure)
        );
    }
    x
}

// NB: `frintx` is technically the correct instruction for C's `rint`. However, in Rust (and LLVM
// by default), `rint` is identical to `roundeven` (no fpenv interaction) so we use the
// side-effect-free `frintn`.
//
// In general, C code that calls Rust's libm should assume that fpenv is ignored.

pub fn rint(mut x: f64) -> f64 {
    // SAFETY: `frintn` is available with neon and has no side effects.
    //
    // `frintn` is always round-to-nearest which does not match the C specification, but Rust does
    // not support rounding modes.
    unsafe {
        asm!(
            "frintn {x:d}, {x:d}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}

pub fn rintf(mut x: f32) -> f32 {
    // SAFETY: `frintn` is available with neon and has no side effects.
    //
    // `frintn` is always round-to-nearest which does not match the C specification, but Rust does
    // not support rounding modes.
    unsafe {
        asm!(
            "frintn {x:s}, {x:s}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}

#[cfg(all(f16_enabled, target_feature = "fp16"))]
pub fn rintf16(mut x: f16) -> f16 {
    // SAFETY: `frintn` is available for `f16` with `fp16` (implies `neon`) and has no side effects.
    //
    // `frintn` is always round-to-nearest which does not match the C specification, but Rust does
    // not support rounding modes.
    unsafe {
        asm!(
            "frintn {x:h}, {x:h}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}

pub fn sqrt(mut x: f64) -> f64 {
    // SAFETY: `fsqrt` is available with neon and has no side effects.
    unsafe {
        asm!(
            "fsqrt {x:d}, {x:d}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}

pub fn sqrtf(mut x: f32) -> f32 {
    // SAFETY: `fsqrt` is available with neon and has no side effects.
    unsafe {
        asm!(
            "fsqrt {x:s}, {x:s}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}

#[cfg(all(f16_enabled, target_feature = "fp16"))]
pub fn sqrtf16(mut x: f16) -> f16 {
    // SAFETY: `fsqrt` is available for `f16` with `fp16` (implies `neon`) and has no
    // side effects.
    unsafe {
        asm!(
            "fsqrt {x:h}, {x:h}",
            x = inout(vreg) x,
            options(nomem, nostack, pure)
        );
    }
    x
}
