const builtin = @import("builtin");
const std = @import("../../std.zig");
const SYS = std.os.linux.SYS;

pub const syscall_arg_t = u32;

pub fn syscall0(
    number: SYS,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall1(
    number: SYS,
    arg1: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall2(
    number: SYS,
    arg1: syscall_arg_t,
    arg2: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
          [arg2] "{o1}" (arg2),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall3(
    number: SYS,
    arg1: syscall_arg_t,
    arg2: syscall_arg_t,
    arg3: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
          [arg2] "{o1}" (arg2),
          [arg3] "{o2}" (arg3),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall4(
    number: SYS,
    arg1: syscall_arg_t,
    arg2: syscall_arg_t,
    arg3: syscall_arg_t,
    arg4: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
          [arg2] "{o1}" (arg2),
          [arg3] "{o2}" (arg3),
          [arg4] "{o3}" (arg4),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall5(
    number: SYS,
    arg1: syscall_arg_t,
    arg2: syscall_arg_t,
    arg3: syscall_arg_t,
    arg4: syscall_arg_t,
    arg5: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
          [arg2] "{o1}" (arg2),
          [arg3] "{o2}" (arg3),
          [arg4] "{o3}" (arg4),
          [arg5] "{o4}" (arg5),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall6(
    number: SYS,
    arg1: syscall_arg_t,
    arg2: syscall_arg_t,
    arg3: syscall_arg_t,
    arg4: syscall_arg_t,
    arg5: syscall_arg_t,
    arg6: syscall_arg_t,
) u32 {
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ neg %%o0
        \\1:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(number)),
          [arg1] "{o0}" (arg1),
          [arg2] "{o1}" (arg2),
          [arg3] "{o2}" (arg3),
          [arg4] "{o3}" (arg4),
          [arg5] "{o4}" (arg5),
          [arg6] "{o5}" (arg6),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn syscall_pipe(
    fd: *[2]std.os.linux.fd_t,
) u32 {
    return asm volatile (
        \\ mov %[arg], %%g3
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ # Return the error code
        \\ ba 2f
        \\  neg %%o0
        \\1:
        \\ st %%o0, [%%g3+0]
        \\ st %%o1, [%%g3+4]
        \\ clr %%o0
        \\2:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(SYS.pipe)),
          [arg] "r" (fd),
        : .{ .memory = true, .g3 = true });
}

pub fn syscall_fork() u32 {
    // Linux/sparc fork() returns two values in %o0 and %o1:
    // - On the parent's side, %o0 is the child's PID and %o1 is 0.
    // - On the child's side, %o0 is the parent's PID and %o1 is 1.
    // We need to clear the child's %o0 so that the return values
    // conform to the libc convention.
    return asm volatile (
        \\ t 0x10
        \\ bcc 1f
        \\  nop
        \\ ba 2f
        \\  neg %%o0
        \\1:
        \\ # Clear the child's %%o0
        \\ dec %%o1
        \\ and %%o1, %%o0, %%o0
        \\2:
        : [ret] "={o0}" (-> u32),
        : [number] "{g1}" (@backingInt(SYS.fork)),
        : .{ .memory = true, .xcc = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true });
}

pub fn clone() callconv(.naked) u32 {
    // __clone(func, stack, flags, arg, ptid, tls, ctid)
    //         i0,   i1,    i2,    i3,  i4,   i5,  sp
    //
    // syscall(SYS_clone, flags, stack, ptid, tls, ctid)
    //         g1         o0,    o1,    o2,   o3,  o4
    asm volatile (
        \\ save %%sp, -96, %%sp
        \\
        \\ // clone() on SPARC can fail with EFAULT if %%sp points to uncommitted memory, so flush
        \\ // all register windows up to this point to ensure that the kernel has enough committed
        \\ // memory for its stack frame.
        \\ save %%sp, -96, %%sp
        \\ t 0x3
        \\ restore
        \\
        \\ # Save the func pointer and the arg pointer
        \\ mov %%i0, %%g2
        \\ mov %%i3, %%g3
        \\
        \\ # Shuffle the arguments
        \\ mov 217, %%g1 // SYS_clone
        \\ mov %%i2, %%o0
        \\
        \\ # Align, and add some extra space for the initial frame
        \\ and %%i1, -8, %%i1
        \\ sub %%i1, 96 + 2047, %%o1
        \\
        \\ mov %%i4, %%o2
        \\ mov %%i5, %%o3
        \\ ld [%%fp + 92 + 2047], %%o4
        \\ t 0x10
        \\ bcs 1f
        \\  nop
        \\ # The child pid is returned in o0 while o1 tells if this
        \\ # process is the child (=1) or the parent (=0).
        \\ tst %%o1
        \\ bne 2f
        \\  nop
        \\
        \\ # Parent process, return the child pid
        \\ mov %%o0, %%i0
        \\ ret
        \\  restore
        \\
        \\1:
        \\ # The syscall failed
        \\ sub %%g0, %%o0, %%i0
        \\ ret
        \\  restore
        \\
        \\2:
        \\ # Child process
    );
    if (builtin.unwind_tables != .none or !builtin.strip_debug_info) asm volatile (
        \\ .cfi_undefined %%i7
    );
    asm volatile (
        \\ mov %%g0, %%fp
        \\ mov %%g0, %%i7
        \\
        \\ # call func(arg)
        \\ call %%g2
        \\  mov %%g3, %%o0
        \\ # Exit
        \\ mov 1, %%g1 // SYS_exit
        \\ t 0x10
    );
}

pub const restore = restore_rt;

pub fn restore_rt() callconv(.naked) noreturn {
    asm volatile (
        \\ nop
        \\ nop
    );
    asm volatile (
        \\ t 0x10
        :
        : [number] "{g1}" (@backingInt(SYS.rt_sigreturn)),
    );
}

pub const VDSO = struct {
    pub const CGT_SYM = "__vdso_clock_gettime";
    pub const CGT_VER = "LINUX_2.6";
};

pub const time_t = i32;
