//@only-target: freebsd
//@compile-flags: -Zmiri-deterministic-concurrency -Zmiri-disable-isolation

use std::mem::{self, MaybeUninit};
use std::ptr::{self, addr_of};
use std::sync::atomic::AtomicU32;
use std::time::Instant;
use std::{io, thread};

fn wait_wake() {
    fn wake_nobody() {
        // Current thread waits on futex.
        // New thread wakes up 0 threads waiting on that futex.
        // Current thread should time out.
        static mut FUTEX: u32 = 0;

        let waker = thread::spawn(|| {
            unsafe {
                assert_eq!(
                    libc::_umtx_op(
                        addr_of!(FUTEX) as *mut _,
                        libc::UMTX_OP_WAKE_PRIVATE,
                        0, // wake up 0 waiters
                        ptr::null_mut::<libc::c_void>(),
                        ptr::null_mut::<libc::c_void>(),
                    ),
                    0
                );
            }
        });

        // 10ms should be enough.
        let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 10_000_000 };
        let timeout_size_arg =
            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
        unsafe {
            assert_eq!(
                libc::_umtx_op(
                    addr_of!(FUTEX) as *mut _,
                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
                    0,
                    timeout_size_arg,
                    &mut timeout as *mut _ as _,
                ),
                -1
            );
            // Main thread did not get woken up, so it timed out.
            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
        }

        waker.join().unwrap();
    }

    fn wake_two_of_three() {
        // We create 2 threads that wait on a futex with a 100ms timeout.
        // The main thread wakes up 2 threads waiting on this futex and after this
        // checks that only those threads woke up and the other one timed out.
        static mut FUTEX: u32 = 0;

        fn waiter() -> bool {
            let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 100_000_000 };
            let timeout_size_arg =
                ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
            unsafe {
                libc::_umtx_op(
                    addr_of!(FUTEX) as *mut _,
                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
                    0, // FUTEX is 0
                    timeout_size_arg,
                    &mut timeout as *mut _ as _,
                );
                // Return true if this thread woke up.
                io::Error::last_os_error().raw_os_error().unwrap() != libc::ETIMEDOUT
            }
        }

        let t1 = thread::spawn(waiter);
        let t2 = thread::spawn(waiter);
        let t3 = thread::spawn(waiter);

        // Run all the waiters, so they can go to sleep.
        thread::yield_now();

        // Wake up 2 thread and make sure 1 is still waiting.
        unsafe {
            assert_eq!(
                libc::_umtx_op(
                    addr_of!(FUTEX) as *mut _,
                    libc::UMTX_OP_WAKE_PRIVATE,
                    2,
                    ptr::null_mut::<libc::c_void>(),
                    ptr::null_mut::<libc::c_void>(),
                ),
                0
            );
        }

        // Treat the booleans as numbers to simplify checking how many threads were woken up.
        let t1 = t1.join().unwrap() as usize;
        let t2 = t2.join().unwrap() as usize;
        let t3 = t3.join().unwrap() as usize;
        let woken_up_count = t1 + t2 + t3;
        assert!(woken_up_count == 2, "Expected 2 threads to wake up got: {woken_up_count}");
    }

    wake_nobody();
    wake_two_of_three();
}

fn wake_dangling() {
    let futex = Box::new(0);
    let ptr: *const u32 = &*futex;
    drop(futex);

    // Expect error since this is now "unmapped" memory.
    unsafe {
        assert_eq!(
            libc::_umtx_op(
                ptr as *const AtomicU32 as *mut _,
                libc::UMTX_OP_WAKE_PRIVATE,
                0,
                ptr::null_mut::<libc::c_void>(),
                ptr::null_mut::<libc::c_void>(),
            ),
            -1
        );
        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EFAULT);
    }
}

fn wait_wrong_val() {
    let futex: u32 = 123;

    // Wait with a wrong value just returns 0
    unsafe {
        assert_eq!(
            libc::_umtx_op(
                ptr::from_ref(&futex).cast_mut().cast(),
                libc::UMTX_OP_WAIT_UINT_PRIVATE,
                456,
                ptr::null_mut::<libc::c_void>(),
                ptr::null_mut::<libc::c_void>(),
            ),
            0
        );
    }
}

fn wait_relative_timeout() {
    fn without_timespec() {
        let start = Instant::now();

        let futex: u32 = 123;

        let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 };
        let timeout_size_arg =
            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
        // Wait for 200ms, with nobody waking us up early
        unsafe {
            assert_eq!(
                libc::_umtx_op(
                    ptr::from_ref(&futex).cast_mut().cast(),
                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
                    123,
                    timeout_size_arg,
                    &mut timeout as *mut _ as _,
                ),
                -1
            );
            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
        }

        assert!((200..1000).contains(&start.elapsed().as_millis()));
    }

    fn with_timespec() {
        let futex: u32 = 123;
        let mut timeout = libc::_umtx_time {
            _timeout: libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 },
            _flags: 0,
            _clockid: libc::CLOCK_MONOTONIC as u32,
        };
        let timeout_size_arg =
            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::_umtx_time>());

        let start = Instant::now();

        // Wait for 200ms, with nobody waking us up early
        unsafe {
            assert_eq!(
                libc::_umtx_op(
                    ptr::from_ref(&futex).cast_mut().cast(),
                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
                    123,
                    timeout_size_arg,
                    &mut timeout as *mut _ as _,
                ),
                -1
            );
            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
        }
        assert!((200..1000).contains(&start.elapsed().as_millis()));
    }

    without_timespec();
    with_timespec();
}

fn wait_absolute_timeout() {
    let start = Instant::now();

    // Get the current monotonic timestamp as timespec.
    let mut timeout = unsafe {
        let mut now: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
        assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0);
        now.assume_init()
    };

    // Add 200ms.
    timeout.tv_nsec += 200_000_000;
    if timeout.tv_nsec > 1_000_000_000 {
        timeout.tv_nsec -= 1_000_000_000;
        timeout.tv_sec += 1;
    }

    // Create umtx_timeout struct with that absolute timeout.
    let umtx_timeout = libc::_umtx_time {
        _timeout: timeout,
        _flags: libc::UMTX_ABSTIME,
        _clockid: libc::CLOCK_MONOTONIC as u32,
    };
    let umtx_timeout_ptr = &umtx_timeout as *const _;
    let umtx_timeout_size = ptr::without_provenance_mut(mem::size_of_val(&umtx_timeout));

    let futex: u32 = 123;

    // Wait for 200ms from now, with nobody waking us up early.
    unsafe {
        assert_eq!(
            libc::_umtx_op(
                ptr::from_ref(&futex).cast_mut().cast(),
                libc::UMTX_OP_WAIT_UINT_PRIVATE,
                123,
                umtx_timeout_size,
                umtx_timeout_ptr as *mut _,
            ),
            -1
        );
        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
    }
    assert!((200..1000).contains(&start.elapsed().as_millis()));
}

fn main() {
    wait_wake();
    wake_dangling();
    wait_wrong_val();
    wait_relative_timeout();
    wait_absolute_timeout();
}
