//@compile-flags: -Zmiri-permissive-provenance
#![feature(ptr_metadata, const_raw_ptr_comparison)]
#![allow(ambiguous_wide_pointer_comparisons)]

use std::mem::{self, transmute};
use std::ptr;

fn one_line_ref() -> i16 {
    *&1
}

fn basic_ref() -> i16 {
    let x = &1;
    *x
}

fn basic_ref_mut() -> i16 {
    let x = &mut 1;
    *x += 2;
    *x
}

fn basic_ref_mut_var() -> i16 {
    let mut a = 1;
    {
        let x = &mut a;
        *x += 2;
    }
    a
}

fn tuple_ref_mut() -> (i8, i8) {
    let mut t = (10, 20);
    {
        let x = &mut t.1;
        *x += 2;
    }
    t
}

fn match_ref_mut() -> i8 {
    let mut t = (20, 22);
    {
        let opt = Some(&mut t);
        match opt {
            Some(&mut (ref mut x, ref mut y)) => *x += *y,
            None => {}
        }
    }
    t.0
}

fn dangling_pointer() -> *const i32 {
    let b = Box::new((42, 42)); // make it bigger than the alignment, so that there is some "room" after this pointer
    &b.0 as *const i32
}

fn wide_ptr_ops() {
    let a: *const dyn Send = &1 as &dyn Send;
    let b: *const dyn Send = &1 as &dyn Send;
    let _val = a == b;
    let _val = a != b;
    let _val = a < b;
    let _val = a <= b;
    let _val = a > b;
    let _val = a >= b;

    let a: *const [u8] = unsafe { transmute((1usize, 1usize)) };
    let b: *const [u8] = unsafe { transmute((1usize, 2usize)) };
    // confirmed with rustc.
    assert!(!(a == b));
    assert!(a != b);
    assert!(a <= b);
    assert!(a < b);
    assert!(!(a >= b));
    assert!(!(a > b));
}

fn metadata_vtable() {
    let p = &0i32 as &dyn std::fmt::Debug;
    let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _);
    assert_eq!(meta.size_of(), mem::size_of::<i32>());
    assert_eq!(meta.align_of(), mem::align_of::<i32>());

    type T = [i32; 16];
    let p = &T::default() as &dyn std::fmt::Debug;
    let meta: ptr::DynMetadata<_> = ptr::metadata(p as *const _);
    assert_eq!(meta.size_of(), mem::size_of::<T>());
    assert_eq!(meta.align_of(), mem::align_of::<T>());
}

fn main() {
    assert_eq!(one_line_ref(), 1);
    assert_eq!(basic_ref(), 1);
    assert_eq!(basic_ref_mut(), 3);
    assert_eq!(basic_ref_mut_var(), 3);
    assert_eq!(tuple_ref_mut(), (10, 22));
    assert_eq!(match_ref_mut(), 42);

    // Compare even dangling pointers with NULL, and with others in the same allocation, including
    // out-of-bounds.
    assert!(dangling_pointer() != std::ptr::null());
    assert!(match dangling_pointer() as usize {
        0 => false,
        _ => true,
    });
    let dangling = dangling_pointer();
    assert!(dangling == dangling);
    assert!(dangling.wrapping_add(1) != dangling);
    assert!(dangling.wrapping_sub(1) != dangling);

    // Compare pointer with BIG integers
    let dangling = dangling as usize;
    assert!(dangling != usize::MAX);
    assert!(dangling != usize::MAX - 1);
    assert!(dangling != usize::MAX - 2);
    assert!(dangling != usize::MAX - 3); // this is even 4-aligned, but it still cannot be equal because of the extra "room" after this pointer
    assert_eq!((usize::MAX - 3) % 4, 0); // just to be sure we got this right

    // Compare pointer with unaligned integers
    assert!(dangling != 1usize);
    assert!(dangling != 2usize);
    assert!(dangling != 3usize);
    // 4 is a possible choice! So we cannot compare with that.
    assert!(dangling != 5usize);
    assert!(dangling != 6usize);
    assert!(dangling != 7usize);

    // Using inequality to do the comparison.
    assert!(dangling > 0);
    assert!(dangling > 1);
    assert!(dangling > 2);
    assert!(dangling > 3);
    assert!(dangling >= 4);

    // CTFE-specific equality tests, need to also work at runtime.
    let addr = &13 as *const i32;
    let addr2 = (addr as usize).wrapping_add(usize::MAX).wrapping_add(1);
    assert_eq!(addr.guaranteed_eq(addr2 as *const _), Some(true));
    assert_eq!(addr.guaranteed_ne(0x100 as *const _), Some(true));

    wide_ptr_ops();
    metadata_vtable();
}
