const std = @import("std");
const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
// self-hosted does not yet support PE executable files / COFF object files
// or mach-o files. So we do the ZIR transform test cases cross compiling for
// x86_64-linux.
const linux_x64 = std.zig.CrossTarget{
    .cpu_arch = .x86_64,
    .os_tag = .linux,
};

pub fn addCases(ctx: *TestContext) !void {
    ctx.transformZIR("referencing decls which appear later in the file", linux_x64,
        \\@void = primitive(void)
        \\@fnty = fntype([], @void, cc=C)
        \\
        \\@9 = str("entry")
        \\@11 = export(@9, "entry")
        \\
        \\@entry = fn(@fnty, {
        \\  %11 = returnvoid()
        \\})
    ,
        \\@void = primitive(void)
        \\@fnty = fntype([], @void, cc=C)
        \\@9 = declref("9__anon_0")
        \\@9__anon_0 = str("entry")
        \\@unnamed$4 = str("entry")
        \\@unnamed$5 = export(@unnamed$4, "entry")
        \\@unnamed$6 = fntype([], @void, cc=C)
        \\@entry = fn(@unnamed$6, {
        \\  %0 = returnvoid() ; deaths=0b1000000000000000
        \\})
        \\
    );
    ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
        \\@void = primitive(void)
        \\@usize = primitive(usize)
        \\@fnty = fntype([], @void, cc=C)
        \\@0 = int(0)
        \\@1 = int(1)
        \\@2 = int(2)
        \\@3 = int(3)
        \\
        \\@entry = fn(@fnty, {
        \\  %a = str("\x32\x08\x01\x0a")
        \\  %a_ref = ref(%a)
        \\  %eptr0 = elemptr(%a_ref, @0)
        \\  %eptr1 = elemptr(%a_ref, @1)
        \\  %eptr2 = elemptr(%a_ref, @2)
        \\  %eptr3 = elemptr(%a_ref, @3)
        \\  %v0 = deref(%eptr0)
        \\  %v1 = deref(%eptr1)
        \\  %v2 = deref(%eptr2)
        \\  %v3 = deref(%eptr3)
        \\  %x0 = add(%v0, %v1)
        \\  %x1 = add(%v2, %v3)
        \\  %result = add(%x0, %x1)
        \\
        \\  %expected = int(69)
        \\  %ok = cmp_eq(%result, %expected)
        \\  %10 = condbr(%ok, {
        \\    %11 = returnvoid()
        \\  }, {
        \\    %12 = breakpoint()
        \\  })
        \\})
        \\
        \\@9 = str("entry")
        \\@11 = export(@9, "entry")
    ,
        \\@void = primitive(void)
        \\@fnty = fntype([], @void, cc=C)
        \\@0 = int(0)
        \\@1 = int(1)
        \\@2 = int(2)
        \\@3 = int(3)
        \\@unnamed$6 = fntype([], @void, cc=C)
        \\@entry = fn(@unnamed$6, {
        \\  %0 = returnvoid() ; deaths=0b1000000000000000
        \\})
        \\@entry__anon_1 = str("2\x08\x01\n")
        \\@9 = declref("9__anon_0")
        \\@9__anon_0 = str("entry")
        \\@unnamed$11 = str("entry")
        \\@unnamed$12 = export(@unnamed$11, "entry")
        \\
    );

    {
        var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64);
        case.addTransform(
            \\@void = primitive(void)
            \\@fnty = fntype([], @void, cc=C)
            \\
            \\@9 = str("entry")
            \\@11 = export(@9, "entry")
            \\
            \\@entry = fn(@fnty, {
            \\  %0 = call(@a, [])
            \\  %1 = returnvoid()
            \\})
            \\
            \\@a = fn(@fnty, {
            \\  %0 = call(@b, [])
            \\  %1 = returnvoid()
            \\})
            \\
            \\@b = fn(@fnty, {
            \\  %0 = call(@a, [])
            \\  %1 = returnvoid()
            \\})
        ,
            \\@void = primitive(void)
            \\@fnty = fntype([], @void, cc=C)
            \\@9 = declref("9__anon_0")
            \\@9__anon_0 = str("entry")
            \\@unnamed$4 = str("entry")
            \\@unnamed$5 = export(@unnamed$4, "entry")
            \\@unnamed$6 = fntype([], @void, cc=C)
            \\@entry = fn(@unnamed$6, {
            \\  %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001
            \\  %1 = returnvoid() ; deaths=0b1000000000000000
            \\})
            \\@unnamed$8 = fntype([], @void, cc=C)
            \\@a = fn(@unnamed$8, {
            \\  %0 = call(@b, [], modifier=auto) ; deaths=0b1000000000000001
            \\  %1 = returnvoid() ; deaths=0b1000000000000000
            \\})
            \\@unnamed$10 = fntype([], @void, cc=C)
            \\@b = fn(@unnamed$10, {
            \\  %0 = call(@a, [], modifier=auto) ; deaths=0b1000000000000001
            \\  %1 = returnvoid() ; deaths=0b1000000000000000
            \\})
            \\
        );
        // Now we introduce a compile error
        case.addError(
            \\@void = primitive(void)
            \\@fnty = fntype([], @void, cc=C)
            \\
            \\@9 = str("entry")
            \\@11 = export(@9, "entry")
            \\
            \\@entry = fn(@fnty, {
            \\  %0 = call(@a, [])
            \\  %1 = returnvoid()
            \\})
            \\
            \\@a = fn(@fnty, {
            \\  %0 = call(@b, [])
            \\  %1 = returnvoid()
            \\})
            \\
            \\@b = fn(@fnty, {
            \\  %9 = compileerror("message")
            \\  %0 = call(@a, [])
            \\  %1 = returnvoid()
            \\})
        ,
            &[_][]const u8{
                ":18:21: error: message",
            },
        );
        // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are
        // referencing either of them. This tests that the cycle is detected, and the error
        // goes away.
        case.addTransform(
            \\@void = primitive(void)
            \\@fnty = fntype([], @void, cc=C)
            \\
            \\@9 = str("entry")
            \\@11 = export(@9, "entry")
            \\
            \\@entry = fn(@fnty, {
            \\  %0 = returnvoid()
            \\})
            \\
            \\@a = fn(@fnty, {
            \\  %0 = call(@b, [])
            \\  %1 = returnvoid()
            \\})
            \\
            \\@b = fn(@fnty, {
            \\  %9 = compileerror("message")
            \\  %0 = call(@a, [])
            \\  %1 = returnvoid()
            \\})
        ,
            \\@void = primitive(void)
            \\@fnty = fntype([], @void, cc=C)
            \\@9 = declref("9__anon_2")
            \\@9__anon_2 = str("entry")
            \\@unnamed$4 = str("entry")
            \\@unnamed$5 = export(@unnamed$4, "entry")
            \\@unnamed$6 = fntype([], @void, cc=C)
            \\@entry = fn(@unnamed$6, {
            \\  %0 = returnvoid() ; deaths=0b1000000000000000
            \\})
            \\
        );
    }

    if (std.Target.current.os.tag != .linux or
        std.Target.current.cpu.arch != .x86_64)
    {
        // TODO implement self-hosted PE (.exe file) linking
        // TODO implement more ZIR so we don't depend on x86_64-linux
        return;
    }

    ctx.compareOutputZIR("hello world ZIR",
        \\@noreturn = primitive(noreturn)
        \\@void = primitive(void)
        \\@usize = primitive(usize)
        \\@0 = int(0)
        \\@1 = int(1)
        \\@2 = int(2)
        \\@3 = int(3)
        \\
        \\@msg = str("Hello, world!\n")
        \\
        \\@start_fnty = fntype([], @noreturn, cc=Naked)
        \\@start = fn(@start_fnty, {
        \\  %SYS_exit_group = int(231)
        \\  %exit_code = as(@usize, @0)
        \\
        \\  %syscall = str("syscall")
        \\  %sysoutreg = str("={rax}")
        \\  %rax = str("{rax}")
        \\  %rdi = str("{rdi}")
        \\  %rcx = str("rcx")
        \\  %rdx = str("{rdx}")
        \\  %rsi = str("{rsi}")
        \\  %r11 = str("r11")
        \\  %memory = str("memory")
        \\
        \\  %SYS_write = as(@usize, @1)
        \\  %STDOUT_FILENO = as(@usize, @1)
        \\
        \\  %msg_addr = ptrtoint(@msg)
        \\
        \\  %len_name = str("len")
        \\  %msg_len_ptr = fieldptr(@msg, %len_name)
        \\  %msg_len = deref(%msg_len_ptr)
        \\  %rc_write = asm(%syscall, @usize,
        \\    volatile=1,
        \\    output=%sysoutreg,
        \\    inputs=[%rax, %rdi, %rsi, %rdx],
        \\    clobbers=[%rcx, %r11, %memory],
        \\    args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
        \\
        \\  %rc_exit = asm(%syscall, @usize,
        \\    volatile=1,
        \\    output=%sysoutreg,
        \\    inputs=[%rax, %rdi],
        \\    clobbers=[%rcx, %r11, %memory],
        \\    args=[%SYS_exit_group, %exit_code])
        \\
        \\  %99 = unreachable()
        \\});
        \\
        \\@9 = str("_start")
        \\@11 = export(@9, "start")
    ,
        \\Hello, world!
        \\
    );

    ctx.compareOutputZIR("function call with no args no return value",
        \\@noreturn = primitive(noreturn)
        \\@void = primitive(void)
        \\@usize = primitive(usize)
        \\@0 = int(0)
        \\@1 = int(1)
        \\@2 = int(2)
        \\@3 = int(3)
        \\
        \\@exit0_fnty = fntype([], @noreturn)
        \\@exit0 = fn(@exit0_fnty, {
        \\  %SYS_exit_group = int(231)
        \\  %exit_code = as(@usize, @0)
        \\
        \\  %syscall = str("syscall")
        \\  %sysoutreg = str("={rax}")
        \\  %rax = str("{rax}")
        \\  %rdi = str("{rdi}")
        \\  %rcx = str("rcx")
        \\  %r11 = str("r11")
        \\  %memory = str("memory")
        \\
        \\  %rc = asm(%syscall, @usize,
        \\    volatile=1,
        \\    output=%sysoutreg,
        \\    inputs=[%rax, %rdi],
        \\    clobbers=[%rcx, %r11, %memory],
        \\    args=[%SYS_exit_group, %exit_code])
        \\
        \\  %99 = unreachable()
        \\});
        \\
        \\@start_fnty = fntype([], @noreturn, cc=Naked)
        \\@start = fn(@start_fnty, {
        \\  %0 = call(@exit0, [])
        \\})
        \\@9 = str("_start")
        \\@11 = export(@9, "start")
    , "");
}
