// Compiler:
//
// Run-time:
//   status: 0

#[cfg(target_arch = "x86_64")]
use std::arch::{asm, global_asm};

#[cfg(target_arch = "x86_64")]
global_asm!(
    "
    .global add_asm
add_asm:
     mov rax, rdi
     add rax, rsi
     ret"
);

#[cfg(target_arch = "x86_64")]
extern "C" {
    fn add_asm(a: i64, b: i64) -> i64;
}

#[cfg(target_arch = "x86_64")]
pub unsafe fn mem_cpy(dst: *mut u8, src: *const u8, len: usize) {
    asm!(
        "rep movsb",
        inout("rdi") dst => _,
        inout("rsi") src => _,
        inout("rcx") len => _,
        options(preserves_flags, nostack)
    );
}

#[cfg(target_arch = "x86_64")]
fn asm() {
    unsafe {
        asm!("nop");
    }

    let x: u64;
    unsafe {
        asm!("mov $5, {}",
            out(reg) x,
            options(att_syntax)
        );
    }
    assert_eq!(x, 5);

    let x: u64;
    let input: u64 = 42;
    unsafe {
        asm!("mov {input}, {output}",
             "add $1, {output}",
            input = in(reg) input,
            output = out(reg) x,
            options(att_syntax)
        );
    }
    assert_eq!(x, 43);

    let x: u64;
    unsafe {
        asm!("mov {}, 6",
            out(reg) x,
        );
    }
    assert_eq!(x, 6);

    let x: u64;
    let input: u64 = 42;
    unsafe {
        asm!("mov {output}, {input}",
             "add {output}, 1",
            input = in(reg) input,
            output = out(reg) x,
        );
    }
    assert_eq!(x, 43);

    // check inout(reg_class) x
    let mut x: u64 = 42;
    unsafe {
        asm!("add {0}, {0}",
            inout(reg) x
        );
    }
    assert_eq!(x, 84);

    // check inout("reg") x
    let mut x: u64 = 42;
    unsafe {
        asm!("add r11, r11",
            inout("r11") x
        );
    }
    assert_eq!(x, 84);

    // check a mix of
    // in("reg")
    // inout(class) x => y
    // inout (class) x
    let x: u64 = 702;
    let y: u64 = 100;
    let res: u64;
    let mut rem: u64 = 0;
    unsafe {
        asm!("div r11",
            in("r11") y,
            inout("eax") x => res,
            inout("edx") rem,
        );
    }
    assert_eq!(res, 7);
    assert_eq!(rem, 2);

    // check const
    let mut x: u64 = 42;
    unsafe {
        asm!("add {}, {}",
            inout(reg) x,
            const 1
        );
    }
    assert_eq!(x, 43);

    // check const (ATT syntax)
    let mut x: u64 = 42;
    unsafe {
        asm!("add ${}, {}",
            const 1,
            inout(reg) x,
            options(att_syntax)
        );
    }
    assert_eq!(x, 43);

    // check sym fn
    extern "C" fn foo() -> u64 {
        42
    }
    let x: u64;
    unsafe {
        asm!("call {}", sym foo, lateout("rax") x);
    }
    assert_eq!(x, 42);

    // check sym fn (ATT syntax)
    let x: u64;
    unsafe {
        asm!("call {}", sym foo, lateout("rax") x, options(att_syntax));
    }
    assert_eq!(x, 42);

    // check sym static
    static FOO: u64 = 42;
    let x: u64;
    unsafe {
        asm!("mov {1}, qword ptr [rip + {0}]", sym FOO, lateout(reg) x);
    }
    assert_eq!(x, 42);

    // check sym static (ATT syntax)
    let x: u64;
    unsafe {
        asm!("movq {0}(%rip), {1}", sym FOO, lateout(reg) x, options(att_syntax));
    }
    assert_eq!(x, 42);

    assert_eq!(unsafe { add_asm(40, 2) }, 42);

    let array1 = [1u8, 2, 3];
    let mut array2 = [0u8, 0, 0];
    unsafe {
        mem_cpy(array2.as_mut_ptr(), array1.as_ptr(), 3);
    }
    assert_eq!(array1, array2);

    // in and clobber registers cannot overlap. This tests that the lateout register without an
    // output place (indicated by the `_`) is not added to the list of clobbered registers
    let x = 8;
    let y: i32;
    unsafe {
        asm!(
            "mov rax, rdi",
            in("rdi") x,
            lateout("rdi") _,
            out("rax") y,
        );
    }
    assert_eq!((x, y), (8, 8));

    // sysv64 is the default calling convention on unix systems. The rdi register is
    // used to pass arguments in the sysv64 calling convention, so this register will be clobbered
    #[cfg(unix)]
    {
        let x = 16;
        let y: i32;
        unsafe {
            asm!(
                "mov rax, rdi",
                in("rdi") x,
                out("rax") y,
                clobber_abi("sysv64"),
            );
        }
        assert_eq!((x, y), (16, 16));
    }

    // the `b` suffix for registers in the `reg_byte` register class is not supported in GCC
    // and needs to be stripped in order to use these registers.
    unsafe {
        core::arch::asm!(
            "",
            out("al") _,
            out("cl") _,
            out("dl") _,
            out("sil") _,
            out("dil") _,
            out("r8b") _,
            out("r9b") _,
            out("r10b") _,
            out("r11b") _,
            out("r12b") _,
            out("r13b") _,
            out("r14b") _,
            out("r15b") _,
        );
    }
}

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

fn main() {
    asm();
}
