#![feature(f128)]
#![allow(unused_macros, unused_features)]

use builtins_test::*;
use compiler_builtins::int::sdiv::{__divmoddi4, __divmodsi4, __divmodti4};
use compiler_builtins::int::udiv::{__udivmoddi4, __udivmodsi4, __udivmodti4, u128_divide_sparc};

// Division algorithms have by far the nastiest and largest number of edge cases, and experience shows
// that sometimes 100_000 iterations of the random fuzzer is needed.

/// Creates intensive test functions for division functions of a certain size
macro_rules! test {
    (
        $n:expr, // the number of bits in a $iX or $uX
        $uX:ident, // unsigned integer that will be shifted
        $iX:ident, // signed version of $uX
        $test_name:ident, // name of the test function
        $unsigned_name:ident, // unsigned division function
        $signed_name:ident // signed division function
    ) => {
        #[test]
        fn $test_name() {
            fuzz_2(N, |lhs, rhs| {
                if rhs == 0 {
                    return;
                }

                let mut rem: $uX = 0;
                let quo: $uX = $unsigned_name(lhs, rhs, Some(&mut rem));
                if rhs <= rem || (lhs != rhs.wrapping_mul(quo).wrapping_add(rem)) {
                    panic!(
                        "unsigned division function failed with lhs:{} rhs:{} \
                        std:({}, {}) builtins:({}, {})",
                        lhs,
                        rhs,
                        lhs.wrapping_div(rhs),
                        lhs.wrapping_rem(rhs),
                        quo,
                        rem
                    );
                }

                // test the signed division function also
                let lhs = lhs as $iX;
                let rhs = rhs as $iX;
                let mut rem: $iX = 0;
                let quo: $iX = $signed_name(lhs, rhs, &mut rem);
                // We cannot just test that
                // `lhs == rhs.wrapping_mul(quo).wrapping_add(rem)`, but also
                // need to make sure the remainder isn't larger than the divisor
                // and has the correct sign.
                let incorrect_rem = if rem == 0 {
                    false
                } else if rhs == $iX::MIN {
                    // `rhs.wrapping_abs()` would overflow, so handle this case
                    // separately.
                    (lhs.is_negative() != rem.is_negative()) || (rem == $iX::MIN)
                } else {
                    (lhs.is_negative() != rem.is_negative())
                        || (rhs.wrapping_abs() <= rem.wrapping_abs())
                };
                if incorrect_rem || lhs != rhs.wrapping_mul(quo).wrapping_add(rem) {
                    panic!(
                        "signed division function failed with lhs:{} rhs:{} \
                        std:({}, {}) builtins:({}, {})",
                        lhs,
                        rhs,
                        lhs.wrapping_div(rhs),
                        lhs.wrapping_rem(rhs),
                        quo,
                        rem
                    );
                }
            });
        }
    };
}

test!(32, u32, i32, div_rem_si4, __udivmodsi4, __divmodsi4);
test!(64, u64, i64, div_rem_di4, __udivmoddi4, __divmoddi4);
test!(128, u128, i128, div_rem_ti4, __udivmodti4, __divmodti4);

#[test]
fn divide_sparc() {
    fuzz_2(N, |lhs, rhs| {
        if rhs == 0 {
            return;
        }

        let mut rem: u128 = 0;
        let quo: u128 = u128_divide_sparc(lhs, rhs, &mut rem);
        if rhs <= rem || (lhs != rhs.wrapping_mul(quo).wrapping_add(rem)) {
            panic!(
                "u128_divide_sparc({}, {}): \
                std:({}, {}), builtins:({}, {})",
                lhs,
                rhs,
                lhs.wrapping_div(rhs),
                lhs.wrapping_rem(rhs),
                quo,
                rem
            );
        }
    });
}

macro_rules! float {
    ($($f:ty, $fn:ident, $apfloat_ty:ident, $sys_available:meta);*;) => {
        $(
            #[test]
            fn $fn() {
                use compiler_builtins::float::{div::$fn, Float};
                use core::ops::Div;

                fuzz_float_2(N, |x: $f, y: $f| {
                    let quo0: $f = apfloat_fallback!($f, $apfloat_ty, $sys_available, Div::div, x, y);
                    let quo1: $f = $fn(x, y);

                    // ARM SIMD instructions always flush subnormals to zero
                    if cfg!(target_arch = "arm") &&
                        ((Float::is_subnormal(quo0)) || Float::is_subnormal(quo1)) {
                        return;
                    }

                    if !Float::eq_repr(quo0, quo1) {
                        panic!(
                            "{}({:?}, {:?}): std: {:?}, builtins: {:?}",
                            stringify!($fn),
                            x,
                            y,
                            quo0,
                            quo1
                        );
                    }
                });
            }
        )*
    };
}

#[cfg(not(x86_no_sse))]
mod float_div {
    use super::*;

    float! {
        f32, __divsf3, Single, all();
        f64, __divdf3, Double, all();
    }

    #[cfg(f128_enabled)]
    #[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
    float! {
        f128, __divtf3, Quad,
        // FIXME(llvm): there is a bug in LLVM rt.
        // See <https://github.com/llvm/llvm-project/issues/91840>.
        not(any(feature = "no-sys-f128", all(target_arch = "aarch64", target_os = "linux")));
    }

    #[cfg(f128_enabled)]
    #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
    float! {
        f128, __divkf3, Quad, not(feature = "no-sys-f128");
    }
}
