//! Encapsulation for system call arguments and return values.
//!
//! The inline-asm and outline-asm code paths do some amount of reordering
//! of arguments; to ensure that we don't accidentally misroute an argument
//! or return value, we use distinct types for each argument index and
//! return value.
//!
//! # Safety
//!
//! The `ToAsm` and `FromAsm` traits are unsafe to use; they should only be
//! used by the syscall code which executes actual syscall machine
//! instructions.

#![allow(unsafe_code)]

use super::c;
use super::fd::RawFd;
use core::marker::PhantomData;

pub(super) trait ToAsm: private::Sealed {
    /// Convert `self` to a `usize` ready to be passed to a syscall
    /// machine instruction.
    ///
    /// # Safety
    ///
    /// This should be used immediately before the syscall instruction, and
    /// the returned value shouldn't be used for any other purpose.
    #[must_use]
    unsafe fn to_asm(self) -> *mut Opaque;
}

pub(super) trait FromAsm: private::Sealed {
    /// Convert `raw` from a value produced by a syscall machine instruction
    /// into a `Self`.
    ///
    /// # Safety
    ///
    /// This should be used immediately after the syscall instruction, and
    /// the operand value shouldn't be used for any other purpose.
    #[must_use]
    unsafe fn from_asm(raw: *mut Opaque) -> Self;
}

/// To preserve provenance, syscall arguments and return values are passed as
/// pointer types. They need a type to point to, so we define a custom private
/// type, to prevent it from being used for anything else.
#[repr(transparent)]
pub(super) struct Opaque(c::c_void);

// Argument numbers.
pub(super) struct A0(());
pub(super) struct A1(());
pub(super) struct A2(());
pub(super) struct A3(());
pub(super) struct A4(());
pub(super) struct A5(());
#[cfg(target_arch = "mips")]
pub(super) struct A6(());
#[cfg(target_arch = "x86")]
pub(super) struct SocketArg;

pub(super) trait ArgNumber: private::Sealed {}
impl ArgNumber for A0 {}
impl ArgNumber for A1 {}
impl ArgNumber for A2 {}
impl ArgNumber for A3 {}
impl ArgNumber for A4 {}
impl ArgNumber for A5 {}
#[cfg(target_arch = "mips")]
impl ArgNumber for A6 {}
#[cfg(target_arch = "x86")]
impl ArgNumber for SocketArg {}

// Return value numbers.
pub(super) struct R0(());

pub(super) trait RetNumber: private::Sealed {}
impl RetNumber for R0 {}

/// Syscall arguments use register-sized types. We use a newtype to
/// discourage accidental misuse of the raw integer values.
///
/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
/// once. And it has a lifetime to ensure that it doesn't outlive any resources
/// it might be pointing to.
#[repr(transparent)]
#[must_use]
pub(super) struct ArgReg<'a, Num: ArgNumber> {
    raw: *mut Opaque,
    _phantom: PhantomData<(&'a (), Num)>,
}

impl<'a, Num: ArgNumber> ToAsm for ArgReg<'a, Num> {
    #[inline]
    unsafe fn to_asm(self) -> *mut Opaque {
        self.raw
    }
}

/// Syscall return values use register-sized types. We use a newtype to
/// discourage accidental misuse of the raw integer values.
///
/// This type doesn't implement `Clone` or `Copy`; it should be used exactly
/// once.
#[repr(transparent)]
#[must_use]
pub(super) struct RetReg<Num: RetNumber> {
    raw: *mut Opaque,
    _phantom: PhantomData<Num>,
}

impl<Num: RetNumber> RetReg<Num> {
    #[inline]
    pub(super) fn decode_usize(self) -> usize {
        debug_assert!(!(-4095..0).contains(&(self.raw as isize)));
        self.raw as usize
    }

    #[inline]
    pub(super) fn decode_raw_fd(self) -> RawFd {
        let bits = self.decode_usize();
        let raw_fd = bits as RawFd;

        // Converting `raw` to `RawFd` should be lossless.
        debug_assert_eq!(raw_fd as usize, bits);

        raw_fd
    }

    #[inline]
    pub(super) fn decode_c_int(self) -> c::c_int {
        let bits = self.decode_usize();
        let c_int_ = bits as c::c_int;

        // Converting `raw` to `c_int` should be lossless.
        debug_assert_eq!(c_int_ as usize, bits);

        c_int_
    }

    #[inline]
    pub(super) fn decode_c_uint(self) -> c::c_uint {
        let bits = self.decode_usize();
        let c_uint_ = bits as c::c_uint;

        // Converting `raw` to `c_uint` should be lossless.
        debug_assert_eq!(c_uint_ as usize, bits);

        c_uint_
    }

    #[inline]
    pub(super) fn decode_void_star(self) -> *mut c::c_void {
        self.raw.cast()
    }

    #[cfg(target_pointer_width = "64")]
    #[inline]
    pub(super) fn decode_u64(self) -> u64 {
        self.decode_usize() as u64
    }

    #[inline]
    pub(super) fn decode_void(self) {
        let ignore = self.decode_usize();
        debug_assert_eq!(ignore, 0);
    }

    #[inline]
    pub(super) fn decode_error_code(self) -> u16 {
        let bits = self.raw as usize;

        // `raw` must be in `-4095..0`. Linux always returns errors in
        // `-4095..0`, and we double-check it here.
        debug_assert!((-4095..0).contains(&(bits as isize)));

        bits as u16
    }

    #[inline]
    pub(super) fn is_nonzero(&self) -> bool {
        !self.raw.is_null()
    }

    #[inline]
    pub(super) fn is_negative(&self) -> bool {
        (self.raw as isize) < 0
    }

    #[inline]
    pub(super) fn is_in_range(&self, range: core::ops::Range<isize>) -> bool {
        range.contains(&(self.raw as isize))
    }
}

impl<Num: RetNumber> FromAsm for RetReg<Num> {
    #[inline]
    unsafe fn from_asm(raw: *mut Opaque) -> Self {
        Self {
            raw,
            _phantom: PhantomData,
        }
    }
}

#[repr(transparent)]
pub(super) struct SyscallNumber<'a> {
    nr: usize,
    _phantom: PhantomData<&'a ()>,
}

impl<'a> ToAsm for SyscallNumber<'a> {
    #[inline]
    unsafe fn to_asm(self) -> *mut Opaque {
        self.nr as usize as *mut Opaque
    }
}

/// Encode a system call argument as an `ArgReg`.
#[inline]
pub(super) fn raw_arg<'a, Num: ArgNumber>(raw: *mut Opaque) -> ArgReg<'a, Num> {
    ArgReg {
        raw,
        _phantom: PhantomData,
    }
}

/// Encode a system call number (a `__NR_*` constant) as a `SyscallNumber`.
#[inline]
pub(super) const fn nr<'a>(nr: u32) -> SyscallNumber<'a> {
    SyscallNumber {
        nr: nr as usize,
        _phantom: PhantomData,
    }
}

/// Seal our various traits using the technique documented [here].
///
/// [here]: https://rust-lang.github.io/api-guidelines/future-proofing.html
mod private {
    pub trait Sealed {}

    // Implement for those same types, but no others.
    impl<'a, Num: super::ArgNumber> Sealed for super::ArgReg<'a, Num> {}
    impl<Num: super::RetNumber> Sealed for super::RetReg<Num> {}
    impl<'a> Sealed for super::SyscallNumber<'a> {}
    impl Sealed for super::A0 {}
    impl Sealed for super::A1 {}
    impl Sealed for super::A2 {}
    impl Sealed for super::A3 {}
    impl Sealed for super::A4 {}
    impl Sealed for super::A5 {}
    #[cfg(target_arch = "mips")]
    impl Sealed for super::A6 {}
    #[cfg(target_arch = "x86")]
    impl Sealed for super::SocketArg {}
    impl Sealed for super::R0 {}
}
