// run-pass
// Checks if the "sysv64" calling convention behaves the same as the
// "C" calling convention on platforms where both should be the same

// This file contains versions of the following run-pass tests with
// the calling convention changed to "sysv64"

// cabi-int-widening
// extern-pass-char
// extern-pass-u32
// extern-pass-u64
// extern-pass-double
// extern-pass-empty
// extern-pass-TwoU8s
// extern-pass-TwoU16s
// extern-pass-TwoU32s
// extern-pass-TwoU64s
// extern-return-TwoU8s
// extern-return-TwoU16s
// extern-return-TwoU32s
// extern-return-TwoU64s
// foreign-fn-with-byval
// issue-28676
// issue-62350-sysv-neg-reg-counts
// struct-return

// ignore-android
// ignore-arm
// ignore-aarch64
// ignore-windows

// note: windows is ignored as rust_test_helpers does not have the sysv64 abi on windows

#[allow(dead_code)]
#[allow(improper_ctypes)]

#[cfg(target_arch = "x86_64")]
mod tests {
    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Debug)]
    pub struct TwoU8s {
        one: u8, two: u8
    }

    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Debug)]
    pub struct TwoU16s {
        one: u16, two: u16
    }

    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Debug)]
    pub struct TwoU32s {
        one: u32, two: u32
    }

    #[repr(C)]
    #[derive(Copy, Clone, PartialEq, Debug)]
    pub struct TwoU64s {
        one: u64, two: u64
    }

    #[repr(C)]
    pub struct ManyInts {
        arg1: i8,
        arg2: i16,
        arg3: i32,
        arg4: i16,
        arg5: i8,
        arg6: TwoU8s,
    }

    #[repr(C)]
    pub struct Empty;

    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct S {
        x: u64,
        y: u64,
        z: u64,
    }

    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Quad { a: u64, b: u64, c: u64, d: u64 }

    #[derive(Copy, Clone)]
    pub struct QuadFloats { a: f32, b: f32, c: f32, d: f32 }

    #[repr(C)]
    #[derive(Copy, Clone)]
    pub struct Floats { a: f64, b: u8, c: f64 }

    #[repr(C, u8)]
    pub enum U8TaggedEnumOptionU64U64 {
        None,
        Some(u64,u64),
    }

    #[repr(C, u8)]
    pub enum U8TaggedEnumOptionU64 {
        None,
        Some(u64),
    }

    #[link(name = "rust_test_helpers", kind = "static")]
    extern "sysv64" {
        pub fn rust_int8_to_int32(_: i8) -> i32;
        pub fn rust_dbg_extern_identity_u8(v: u8) -> u8;
        pub fn rust_dbg_extern_identity_u32(v: u32) -> u32;
        pub fn rust_dbg_extern_identity_u64(v: u64) -> u64;
        pub fn rust_dbg_extern_identity_double(v: f64) -> f64;
        pub fn rust_dbg_extern_empty_struct(v1: ManyInts, e: Empty, v2: ManyInts);
        pub fn rust_dbg_extern_identity_TwoU8s(v: TwoU8s) -> TwoU8s;
        pub fn rust_dbg_extern_identity_TwoU16s(v: TwoU16s) -> TwoU16s;
        pub fn rust_dbg_extern_identity_TwoU32s(v: TwoU32s) -> TwoU32s;
        pub fn rust_dbg_extern_identity_TwoU64s(v: TwoU64s) -> TwoU64s;
        pub fn rust_dbg_extern_return_TwoU8s() -> TwoU8s;
        pub fn rust_dbg_extern_return_TwoU16s() -> TwoU16s;
        pub fn rust_dbg_extern_return_TwoU32s() -> TwoU32s;
        pub fn rust_dbg_extern_return_TwoU64s() -> TwoU64s;
        pub fn get_x(x: S) -> u64;
        pub fn get_y(x: S) -> u64;
        pub fn get_z(x: S) -> u64;
        pub fn get_c_many_params(_: *const (), _: *const (),
                                 _: *const (), _: *const (), f: Quad) -> u64;
        pub fn get_c_exhaust_sysv64_ints(
            _: *const (),
            _: *const (),
            _: *const (),
            _: *const (),
            _: *const (),
            _: *const (),
            _: *const (),
            h: QuadFloats,
        ) -> f32;
        pub fn rust_dbg_abi_1(q: Quad) -> Quad;
        pub fn rust_dbg_abi_2(f: Floats) -> Floats;
        pub fn rust_dbg_new_some_u64u64(a: u64, b: u64) -> U8TaggedEnumOptionU64U64;
        pub fn rust_dbg_new_none_u64u64() -> U8TaggedEnumOptionU64U64;
        pub fn rust_dbg_unpack_option_u64u64(
            o: U8TaggedEnumOptionU64U64,
            a: *mut u64,
            b: *mut u64,
        ) -> i32;
        pub fn rust_dbg_new_some_u64(some: u64) -> U8TaggedEnumOptionU64;
        pub fn rust_dbg_new_none_u64() -> U8TaggedEnumOptionU64;
        pub fn rust_dbg_unpack_option_u64(o: U8TaggedEnumOptionU64, v: *mut u64) -> i32;
    }

    pub fn cabi_int_widening() {
        let x = unsafe {
            rust_int8_to_int32(-1)
        };

        assert!(x == -1);
    }

    pub fn extern_pass_char() {
        unsafe {
            assert_eq!(22, rust_dbg_extern_identity_u8(22));
        }
    }

    pub fn extern_pass_u32() {
        unsafe {
            assert_eq!(22, rust_dbg_extern_identity_u32(22));
        }
    }

    pub fn extern_pass_u64() {
        unsafe {
            assert_eq!(22, rust_dbg_extern_identity_u64(22));
        }
    }

    pub fn extern_pass_double() {
        unsafe {
            assert_eq!(22.0_f64, rust_dbg_extern_identity_double(22.0_f64));
        }
    }

    pub fn extern_pass_empty() {
        unsafe {
            let x = ManyInts {
                arg1: 2,
                arg2: 3,
                arg3: 4,
                arg4: 5,
                arg5: 6,
                arg6: TwoU8s { one: 7, two: 8, }
            };
            let y = ManyInts {
                arg1: 1,
                arg2: 2,
                arg3: 3,
                arg4: 4,
                arg5: 5,
                arg6: TwoU8s { one: 6, two: 7, }
            };
            let empty = Empty;
            rust_dbg_extern_empty_struct(x, empty, y);
        }
    }

    pub fn extern_pass_twou8s() {
        unsafe {
            let x = TwoU8s {one: 22, two: 23};
            let y = rust_dbg_extern_identity_TwoU8s(x);
            assert_eq!(x, y);
        }
    }

    pub fn extern_pass_twou16s() {
        unsafe {
            let x = TwoU16s {one: 22, two: 23};
            let y = rust_dbg_extern_identity_TwoU16s(x);
            assert_eq!(x, y);
        }
    }

    pub fn extern_pass_twou32s() {
        unsafe {
            let x = TwoU32s {one: 22, two: 23};
            let y = rust_dbg_extern_identity_TwoU32s(x);
            assert_eq!(x, y);
        }
    }

    pub fn extern_pass_twou64s() {
        unsafe {
            let x = TwoU64s {one: 22, two: 23};
            let y = rust_dbg_extern_identity_TwoU64s(x);
            assert_eq!(x, y);
        }
    }

    pub fn extern_return_twou8s() {
        unsafe {
            let y = rust_dbg_extern_return_TwoU8s();
            assert_eq!(y.one, 10);
            assert_eq!(y.two, 20);
        }
    }

    pub fn extern_return_twou16s() {
        unsafe {
            let y = rust_dbg_extern_return_TwoU16s();
            assert_eq!(y.one, 10);
            assert_eq!(y.two, 20);
        }
    }

    pub fn extern_return_twou32s() {
        unsafe {
            let y = rust_dbg_extern_return_TwoU32s();
            assert_eq!(y.one, 10);
            assert_eq!(y.two, 20);
        }
    }

    pub fn extern_return_twou64s() {
        unsafe {
            let y = rust_dbg_extern_return_TwoU64s();
            assert_eq!(y.one, 10);
            assert_eq!(y.two, 20);
        }
    }

    #[inline(never)]
    fn indirect_call(func: unsafe extern "sysv64" fn(s: S) -> u64, s: S) -> u64 {
        unsafe {
            func(s)
        }
    }

    pub fn foreign_fn_with_byval() {
        let s = S { x: 1, y: 2, z: 3 };
        assert_eq!(s.x, indirect_call(get_x, s));
        assert_eq!(s.y, indirect_call(get_y, s));
        assert_eq!(s.z, indirect_call(get_z, s));
    }

    fn test() {
        use std::ptr;
        unsafe {
            let null = ptr::null();
            let q = Quad {
                a: 1,
                b: 2,
                c: 3,
                d: 4
            };
            assert_eq!(get_c_many_params(null, null, null, null, q), q.c);
        }
    }

    pub fn issue_28676() {
        test();
    }

    fn test_62350() {
        use std::ptr;
        unsafe {
            let null = ptr::null();
            let q = QuadFloats {
                a: 10.2,
                b: 20.3,
                c: 30.4,
                d: 40.5
            };
            assert_eq!(
                get_c_exhaust_sysv64_ints(null, null, null, null, null, null, null, q),
                q.c,
            );
        }
    }

    pub fn issue_62350() {
        test_62350();
    }

    fn test1() {
        unsafe {
            let q = Quad { a: 0xaaaa_aaaa_aaaa_aaaa,
                     b: 0xbbbb_bbbb_bbbb_bbbb,
                     c: 0xcccc_cccc_cccc_cccc,
                     d: 0xdddd_dddd_dddd_dddd };
            let qq = rust_dbg_abi_1(q);
            println!("a: {:x}", qq.a as usize);
            println!("b: {:x}", qq.b as usize);
            println!("c: {:x}", qq.c as usize);
            println!("d: {:x}", qq.d as usize);
            assert_eq!(qq.a, q.c + 1);
            assert_eq!(qq.b, q.d - 1);
            assert_eq!(qq.c, q.a + 1);
            assert_eq!(qq.d, q.b - 1);
        }
    }

    fn test2() {
        unsafe {
            let f = Floats { a: 1.234567890e-15_f64,
                     b: 0b_1010_1010,
                     c: 1.0987654321e-15_f64 };
            let ff = rust_dbg_abi_2(f);
            println!("a: {}", ff.a as f64);
            println!("b: {}", ff.b as usize);
            println!("c: {}", ff.c as f64);
            assert_eq!(ff.a, f.c + 1.0f64);
            assert_eq!(ff.b, 0xff);
            assert_eq!(ff.c, f.a - 1.0f64);
        }
    }

    pub fn struct_return() {
        test1();
        test2();
    }

    pub fn enum_passing_and_return_pair() {
        let some_u64u64 = unsafe { rust_dbg_new_some_u64u64(10, 20) };
        if let U8TaggedEnumOptionU64U64::Some(a, b) = some_u64u64 {
            assert_eq!(10, a);
            assert_eq!(20, b);
        } else {
            panic!("unexpected none");
        }

        let none_u64u64 = unsafe { rust_dbg_new_none_u64u64() };
        if let U8TaggedEnumOptionU64U64::Some(_,_) = none_u64u64 {
            panic!("unexpected some");
        }

        let mut a: u64 = 0;
        let mut b: u64 = 0;
        let r = unsafe {
            rust_dbg_unpack_option_u64u64(some_u64u64, &mut a as *mut _, &mut b as *mut _)
        };
        assert_eq!(1, r);
        assert_eq!(10, a);
        assert_eq!(20, b);

        let mut a: u64 = 0;
        let mut b: u64 = 0;
        let r = unsafe {
            rust_dbg_unpack_option_u64u64(none_u64u64, &mut a as *mut _, &mut b as *mut _)
        };
        assert_eq!(0, r);
        assert_eq!(0, a);
        assert_eq!(0, b);
    }

    pub fn enum_passing_and_return() {
        let some_u64 = unsafe { rust_dbg_new_some_u64(10) };
        if let U8TaggedEnumOptionU64::Some(v) = some_u64 {
            assert_eq!(10, v);
        } else {
            panic!("unexpected none");
        }

        let none_u64 = unsafe { rust_dbg_new_none_u64() };
        if let U8TaggedEnumOptionU64::Some(_) = none_u64 {
            panic!("unexpected some");
        }

        let mut target: u64 = 0;
        let r = unsafe { rust_dbg_unpack_option_u64(some_u64, &mut target as *mut _) };
        assert_eq!(1, r);
        assert_eq!(10, target);

        let mut target: u64 = 0;
        let r = unsafe { rust_dbg_unpack_option_u64(none_u64, &mut target as *mut _) };
        assert_eq!(0, r);
        assert_eq!(0, target);
    }
}

#[cfg(target_arch = "x86_64")]
fn main() {
    use tests::*;
    cabi_int_widening();
    extern_pass_char();
    extern_pass_u32();
    extern_pass_u64();
    extern_pass_double();
    extern_pass_empty();
    extern_pass_twou8s();
    extern_pass_twou16s();
    extern_pass_twou32s();
    extern_pass_twou64s();
    extern_return_twou8s();
    extern_return_twou16s();
    extern_return_twou32s();
    extern_return_twou64s();
    foreign_fn_with_byval();
    issue_28676();
    issue_62350();
    struct_return();
    enum_passing_and_return_pair();
    enum_passing_and_return();
}

#[cfg(not(target_arch = "x86_64"))]
fn main() {

}
