use super::*;

#[test]
fn size_of() {
    check_number(
        r#"
        #[rustc_intrinsic]
        pub fn size_of<T>() -> usize;

        const GOAL: usize = size_of::<i32>();
        "#,
        4,
    );
}

#[test]
fn size_of_val() {
    check_number(
        r#"
        //- minicore: coerce_unsized
        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        struct X(i32, u8);

        const GOAL: usize = size_of_val(&X(1, 2));
        "#,
        8,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized
        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        const GOAL: usize = {
            let it: &[i32] = &[1, 2, 3];
            size_of_val(it)
        };
        "#,
        12,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized, transmute
        use core::mem::transmute;

        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        struct X {
            x: i64,
            y: u8,
            t: [i32],
        }

        const GOAL: usize = unsafe {
            let y: &X = transmute([0usize, 3]);
            size_of_val(y)
        };
        "#,
        24,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized, transmute
        use core::mem::transmute;

        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        struct X {
            x: i32,
            y: i64,
            t: [u8],
        }

        const GOAL: usize = unsafe {
            let y: &X = transmute([0usize, 15]);
            size_of_val(y)
        };
    "#,
        32,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized, fmt, builtin_impls, dispatch_from_dyn
        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        const GOAL: usize = {
            let x: &i16 = &5;
            let y: &dyn core::fmt::Debug = x;
            let z: &dyn core::fmt::Debug = &y;
            size_of_val(x) + size_of_val(y) * 10 + size_of_val(z) * 100
        };
        "#,
        1622,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized
        #[rustc_intrinsic]
        pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;

        const GOAL: usize = {
            size_of_val("salam")
        };
        "#,
        5,
    );
}

#[test]
fn min_align_of_val() {
    check_number(
        r#"
        //- minicore: coerce_unsized
        #[rustc_intrinsic]
        pub fn min_align_of_val<T: ?Sized>(_: *const T) -> usize;

        struct X(i32, u8);

        const GOAL: usize = min_align_of_val(&X(1, 2));
        "#,
        4,
    );
    check_number(
        r#"
        //- minicore: coerce_unsized
        #[rustc_intrinsic]
        pub fn min_align_of_val<T: ?Sized>(_: *const T) -> usize;

        const GOAL: usize = {
            let x: &[i32] = &[1, 2, 3];
            min_align_of_val(x)
        };
        "#,
        4,
    );
}

#[test]
fn type_name() {
    check_str(
        r#"
        #[rustc_intrinsic]
        pub fn type_name<T: ?Sized>() -> &'static str;

        const GOAL: &str = type_name::<i32>();
        "#,
        "i32",
    );
    check_str(
        r#"
        #[rustc_intrinsic]
        pub fn type_name<T: ?Sized>() -> &'static str;

        mod mod1 {
            pub mod mod2 {
                pub struct Ty;
            }
        }

        const GOAL: &str = type_name::<mod1::mod2::Ty>();
        "#,
        "mod1::mod2::Ty",
    );
}

#[test]
fn transmute() {
    check_number(
        r#"
        #[rustc_intrinsic]
        pub fn transmute<T, U>(e: T) -> U;

        const GOAL: i32 = transmute((1i16, 1i16));
        "#,
        0x00010001,
    );
}

#[test]
fn read_via_copy() {
    check_number(
        r#"
        #[rustc_intrinsic]
        pub fn read_via_copy<T>(e: *const T) -> T;
        #[rustc_intrinsic]
        pub fn volatile_load<T>(e: *const T) -> T;

        const GOAL: i32 = {
            let x = 2;
            read_via_copy(&x) + volatile_load(&x)
        };
        "#,
        4,
    );
}

#[test]
fn const_eval_select() {
    check_number(
        r#"
        //- minicore: fn
        extern "rust-intrinsic" {
            pub fn const_eval_select<ARG, F, G, RET>(arg: ARG, called_in_const: F, called_at_rt: G) -> RET
            where
                G: FnOnce<ARG, Output = RET>,
                F: FnOnce<ARG, Output = RET>;
        }

        const fn in_const(x: i32, y: i32) -> i32 {
            x + y
        }

        fn in_rt(x: i32, y: i32) -> i32 {
            x + y
        }

        const GOAL: i32 = const_eval_select((2, 3), in_const, in_rt);
        "#,
        5,
    );
}

#[test]
fn wrapping_add() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn wrapping_add<T>(a: T, b: T) -> T;
        }

        const GOAL: u8 = wrapping_add(10, 250);
        "#,
        4,
    );
}

#[test]
fn ptr_offset_from() {
    check_number(
        r#"
        //- minicore: index, slice, coerce_unsized
        extern "rust-intrinsic" {
            pub fn ptr_offset_from<T>(ptr: *const T, base: *const T) -> isize;
            pub fn ptr_offset_from_unsigned<T>(ptr: *const T, base: *const T) -> usize;
        }

        const GOAL: isize = {
            let x = [1, 2, 3, 4, 5i32];
            let r1 = -ptr_offset_from(&x[0], &x[4]);
            let r2 = ptr_offset_from(&x[3], &x[1]);
            let r3 = ptr_offset_from_unsigned(&x[3], &x[0]) as isize;
            r3 * 100 + r2 * 10 + r1
        };
        "#,
        324,
    );
}

#[test]
fn saturating() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn saturating_add<T>(a: T, b: T) -> T;
        }

        const GOAL: u8 = saturating_add(10, 250);
        "#,
        255,
    );
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn saturating_sub<T>(a: T, b: T) -> T;
        }

        const GOAL: bool = saturating_sub(5u8, 7) == 0 && saturating_sub(8u8, 4) == 4;
        "#,
        1,
    );
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn saturating_add<T>(a: T, b: T) -> T;
        }

        const GOAL: i8 = saturating_add(5, 8);
        "#,
        13,
    );
}

#[test]
fn allocator() {
    check_number(
        r#"
        //- minicore: sized
        extern "Rust" {
            #[rustc_allocator]
            fn __rust_alloc(size: usize, align: usize) -> *mut u8;
            #[rustc_deallocator]
            fn __rust_dealloc(ptr: *mut u8, size: usize, align: usize);
            #[rustc_reallocator]
            fn __rust_realloc(ptr: *mut u8, old_size: usize, align: usize, new_size: usize) -> *mut u8;
            #[rustc_allocator_zeroed]
            fn __rust_alloc_zeroed(size: usize, align: usize) -> *mut u8;
        }

        const GOAL: u8 = unsafe {
            let ptr = __rust_alloc(4, 1);
            let ptr2 = ((ptr as usize) + 1) as *mut u8;
            *ptr = 23;
            *ptr2 = 32;
            let ptr = __rust_realloc(ptr, 4, 1, 8);
            let ptr = __rust_realloc(ptr, 8, 1, 3);
            let ptr2 = ((ptr as usize) + 1) as *mut u8;
            *ptr + *ptr2
        };
        "#,
        55,
    );
}

#[test]
fn overflowing_add() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn add_with_overflow<T>(x: T, y: T) -> (T, bool);
        }

        const GOAL: u8 = add_with_overflow(1, 2).0;
        "#,
        3,
    );
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn add_with_overflow<T>(x: T, y: T) -> (T, bool);
        }

        const GOAL: u8 = add_with_overflow(1, 2).1 as u8;
        "#,
        0,
    );
}

#[test]
fn needs_drop() {
    check_number(
        r#"
        //- minicore: copy, sized
        extern "rust-intrinsic" {
            pub fn needs_drop<T: ?Sized>() -> bool;
        }
        struct X;
        const GOAL: bool = !needs_drop::<i32>() && needs_drop::<X>();
        "#,
        1,
    );
}

#[test]
fn discriminant_value() {
    check_number(
        r#"
        //- minicore: discriminant, option
        use core::marker::DiscriminantKind;
        extern "rust-intrinsic" {
            pub fn discriminant_value<T>(v: &T) -> <T as DiscriminantKind>::Discriminant;
        }
        const GOAL: bool = {
            discriminant_value(&Some(2i32)) == discriminant_value(&Some(5i32))
                && discriminant_value(&Some(2i32)) != discriminant_value(&None::<i32>)
        };
        "#,
        1,
    );
}

#[test]
fn likely() {
    check_number(
        r#"
        #[rustc_intrinsic]
        pub const fn likely(b: bool) -> bool {
            b
        }

        #[rustc_intrinsic]
        pub const fn unlikely(b: bool) -> bool {
            b
        }

        const GOAL: bool = likely(true) && unlikely(true) && !likely(false) && !unlikely(false);
        "#,
        1,
    );
}

#[test]
fn floating_point() {
    // FIXME(#17451): Add `f16` and `f128` tests once intrinsics are added.
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn sqrtf32(x: f32) -> f32;
            pub fn powf32(a: f32, x: f32) -> f32;
            pub fn fmaf32(a: f32, b: f32, c: f32) -> f32;
        }

        const GOAL: f32 = sqrtf32(1.2) + powf32(3.4, 5.6) + fmaf32(-7.8, 1.3, 2.4);
        "#,
        i128::from_le_bytes(pad16(
            &f32::to_le_bytes(1.2f32.sqrt() + 3.4f32.powf(5.6) + (-7.8f32).mul_add(1.3, 2.4)),
            true,
        )),
    );
    #[allow(unknown_lints, clippy::unnecessary_min_or_max)]
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn powif64(a: f64, x: i32) -> f64;
            pub fn sinf64(x: f64) -> f64;
            pub fn minnumf64(x: f64, y: f64) -> f64;
        }

        const GOAL: f64 = powif64(1.2, 5) + sinf64(3.4) + minnumf64(-7.8, 1.3);
        "#,
        i128::from_le_bytes(pad16(
            &f64::to_le_bytes(1.2f64.powi(5) + 3.4f64.sin() + (-7.8f64).min(1.3)),
            true,
        )),
    );
}

#[test]
fn atomic() {
    check_number(
        r#"
        //- minicore: copy
        extern "rust-intrinsic" {
            pub fn atomic_load_seqcst<T: Copy>(src: *const T) -> T;
            pub fn atomic_xchg_acquire<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_cxchg_release_seqcst<T: Copy>(dst: *mut T, old: T, src: T) -> (T, bool);
            pub fn atomic_cxchgweak_acquire_acquire<T: Copy>(dst: *mut T, old: T, src: T) -> (T, bool);
            pub fn atomic_store_release<T: Copy>(dst: *mut T, val: T);
            pub fn atomic_xadd_acqrel<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_xsub_seqcst<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_and_acquire<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_nand_seqcst<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_or_release<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_xor_seqcst<T: Copy>(dst: *mut T, src: T) -> T;
            pub fn atomic_fence_seqcst();
            pub fn atomic_singlethreadfence_acqrel();
        }

        fn should_not_reach() {
            _ // fails the test if executed
        }

        const GOAL: i32 = {
            let mut x = 5;
            atomic_store_release(&mut x, 10);
            let mut y = atomic_xchg_acquire(&mut x, 100);
            atomic_xadd_acqrel(&mut y, 20);
            if (30, true) != atomic_cxchg_release_seqcst(&mut y, 30, 40) {
                should_not_reach();
            }
            atomic_fence_seqcst();
            if (40, false) != atomic_cxchg_release_seqcst(&mut y, 30, 50) {
                should_not_reach();
            }
            if (40, true) != atomic_cxchgweak_acquire_acquire(&mut y, 40, 30) {
                should_not_reach();
            }
            let mut z = atomic_xsub_seqcst(&mut x, -200);
            atomic_singlethreadfence_acqrel();
            atomic_xor_seqcst(&mut x, 1024);
            atomic_load_seqcst(&x) + z * 3 + atomic_load_seqcst(&y) * 2
        };
        "#,
        660 + 1024,
    );
}

#[test]
fn offset() {
    check_number(
        r#"
        //- minicore: coerce_unsized, index, slice
        extern "rust-intrinsic" {
            pub fn offset<Ptr, Delta>(dst: Ptr, offset: Delta) -> Ptr;
            pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
        }

        const GOAL: i32 = unsafe {
            let ar: &[(i32, i32, i32)] = &[
                (10, 11, 12),
                (20, 21, 22),
                (30, 31, 32),
                (40, 41, 42),
                (50, 51, 52),
            ];
            let ar: *const [(i32, i32, i32)] = ar;
            let ar = ar as *const (i32, i32, i32);
            let element3 = *offset(ar, 2usize);
            let element4 = *arith_offset(ar, 3);
            element3.1 * 100 + element4.0
        };
        "#,
        3140,
    );
}

#[test]
fn arith_offset() {
    check_number(
        r#"
        //- minicore: coerce_unsized, index, slice
        extern "rust-intrinsic" {
            pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
        }

        const GOAL: u8 = unsafe {
            let ar: &[(u8, u8, u8)] = &[
                (10, 11, 12),
                (20, 21, 22),
                (30, 31, 32),
                (40, 41, 42),
                (50, 51, 52),
            ];
            let ar: *const [(u8, u8, u8)] = ar;
            let ar = ar as *const (u8, u8, u8);
            let element = *arith_offset(arith_offset(ar, 102), -100);
            element.1
        };
        "#,
        31,
    );
}

#[test]
fn copy_nonoverlapping() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
        }

        const GOAL: u8 = unsafe {
            let mut x = 2;
            let y = 5;
            copy_nonoverlapping(&y, &mut x, 1);
            x
        };
        "#,
        5,
    );
}

#[test]
fn write_bytes() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
        }

        const GOAL: i32 = unsafe {
            let mut x = 2;
            write_bytes(&mut x, 5, 1);
            x
        };
        "#,
        0x05050505,
    );
}

#[test]
fn write_via_move() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            fn write_via_move<T>(ptr: *mut T, value: T);
        }

        const GOAL: i32 = unsafe {
            let mut x = 2;
            write_via_move(&mut x, 100);
            x
        };
        "#,
        100,
    );
}

#[test]
fn copy() {
    check_number(
        r#"
        //- minicore: coerce_unsized, index, slice
        extern "rust-intrinsic" {
            pub fn copy<T>(src: *const T, dst: *mut T, count: usize);
        }

        const GOAL: i32 = unsafe {
            let mut x = [1i32, 2, 3, 4, 5];
            let y = (&mut x as *mut _) as *mut i32;
            let z = (y as usize + 4) as *const i32;
            copy(z, y, 4);
            x[0] + x[1] + x[2] + x[3] + x[4]
        };
        "#,
        19,
    );
}

#[test]
fn ctpop() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn ctpop<T: Copy>(x: T) -> T;
        }

        const GOAL: i64 = ctpop(-29);
        "#,
        61,
    );
}

#[test]
fn ctlz() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn ctlz<T: Copy>(x: T) -> T;
        }

        const GOAL: u8 = ctlz(0b0001_1100_u8);
        "#,
        3,
    );
}

#[test]
fn cttz() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn cttz<T: Copy>(x: T) -> T;
        }

        const GOAL: i64 = cttz(-24);
        "#,
        3,
    );
}

#[test]
fn rotate() {
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
        }

        const GOAL: i64 = rotate_left(0xaa00000000006e1i64, 12);
        "#,
        0x6e10aa,
    );
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn rotate_right<T: Copy>(x: T, y: T) -> T;
        }

        const GOAL: i64 = rotate_right(0x6e10aa, 12);
        "#,
        0xaa00000000006e1,
    );
    check_number(
        r#"
        extern "rust-intrinsic" {
            pub fn rotate_left<T: Copy>(x: T, y: T) -> T;
        }

        const GOAL: i8 = rotate_left(129, 2);
        "#,
        6,
    );
    check_number(
        r#"
        #[rustc_intrinsic]
        pub fn rotate_right<T: Copy>(x: T, y: T) -> T;

        const GOAL: i32 = rotate_right(10006016, 1020315);
        "#,
        320192512,
    );
}

#[test]
fn simd() {
    check_number(
        r#"
        pub struct i8x16(
            i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,
        );
        #[rustc_intrinsic]
        pub fn simd_bitmask<T, U>(x: T) -> U;
        const GOAL: u16 = simd_bitmask(i8x16(
            0, 1, 0, 0, 2, 255, 100, 0, 50, 0, 1, 1, 0, 0, 0, 0
        ));
        "#,
        0b0000110101110010,
    );
    check_number(
        r#"
        pub struct i8x16(
            i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,i8,
        );
        #[rustc_intrinsic]
        pub fn simd_lt<T, U>(x: T, y: T) -> U;
        #[rustc_intrinsic]
        pub fn simd_bitmask<T, U>(x: T) -> U;
        const GOAL: u16 = simd_bitmask(simd_lt::<i8x16, i8x16>(
            i8x16(
                -105, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
            ),
            i8x16(
                -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
            ),
        ));
        "#,
        0xFFFF,
    );
}
