diff --git a/compiler/rustc_mir/src/interpret/intrinsics.rs b/compiler/rustc_mir/src/interpret/intrinsics.rs
index 474e1f8e577f8..c182fa35ee24a 100644
--- a/compiler/rustc_mir/src/interpret/intrinsics.rs
+++ b/compiler/rustc_mir/src/interpret/intrinsics.rs
@@ -322,6 +322,29 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let result = Scalar::from_uint(truncated_bits, layout.size);
                 self.write_scalar(result, dest)?;
             }
+            sym::copy | sym::copy_nonoverlapping => {
+                let elem_ty = instance.substs.type_at(0);
+                let elem_layout = self.layout_of(elem_ty)?;
+                let count = self.read_scalar(args[2])?.to_machine_usize(self)?;
+                let elem_align = elem_layout.align.abi;
+
+                let size = elem_layout.size.checked_mul(count, self).ok_or_else(|| {
+                    err_ub_format!("overflow computing total size of `{}`", intrinsic_name)
+                })?;
+                let src = self.read_scalar(args[0])?.check_init()?;
+                let src = self.memory.check_ptr_access(src, size, elem_align)?;
+                let dest = self.read_scalar(args[1])?.check_init()?;
+                let dest = self.memory.check_ptr_access(dest, size, elem_align)?;
+
+                if let (Some(src), Some(dest)) = (src, dest) {
+                    self.memory.copy(
+                        src,
+                        dest,
+                        size,
+                        intrinsic_name == sym::copy_nonoverlapping,
+                    )?;
+                }
+            }
             sym::offset => {
                 let ptr = self.read_scalar(args[0])?.check_init()?;
                 let offset_count = self.read_scalar(args[1])?.to_machine_isize(self)?;
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index 5292182269385..87863ab5c68f4 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -1846,20 +1846,22 @@ pub(crate) fn is_nonoverlapping<T>(src: *const T, dst: *const T, count: usize) -
 /// [`Vec::append`]: ../../std/vec/struct.Vec.html#method.append
 #[doc(alias = "memcpy")]
 #[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "const_intrinsic_copy", issue = "none")]
 #[inline]
-pub unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize) {
+pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize) {
     extern "rust-intrinsic" {
         fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize);
     }
 
-    if cfg!(debug_assertions)
+    // FIXME: Perform these checks only at run time
+    /*if cfg!(debug_assertions)
         && !(is_aligned_and_not_null(src)
             && is_aligned_and_not_null(dst)
             && is_nonoverlapping(src, dst, count))
     {
         // Not panicking to keep codegen impact smaller.
         abort();
-    }
+    }*/
 
     // SAFETY: the safety contract for `copy_nonoverlapping` must be
     // upheld by the caller.
@@ -1928,16 +1930,19 @@ pub unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: usize) {
 /// ```
 #[doc(alias = "memmove")]
 #[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "const_intrinsic_copy", issue = "none")]
 #[inline]
-pub unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
+pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
     extern "rust-intrinsic" {
+        #[rustc_const_unstable(feature = "const_intrinsic_copy", issue = "none")]
         fn copy<T>(src: *const T, dst: *mut T, count: usize);
     }
 
-    if cfg!(debug_assertions) && !(is_aligned_and_not_null(src) && is_aligned_and_not_null(dst)) {
+    // FIXME: Perform these checks only at run time
+    /*if cfg!(debug_assertions) && !(is_aligned_and_not_null(src) && is_aligned_and_not_null(dst)) {
         // Not panicking to keep codegen impact smaller.
         abort();
-    }
+    }*/
 
     // SAFETY: the safety contract for `copy` must be upheld by the caller.
     unsafe { copy(src, dst, count) }
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 5b19bf6b80f38..4a3020d6b99e1 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -73,6 +73,7 @@
 #![feature(const_assert_type)]
 #![feature(const_discriminant)]
 #![feature(const_cell_into_inner)]
+#![feature(const_intrinsic_copy)]
 #![feature(const_checked_int_methods)]
 #![feature(const_euclidean_int_methods)]
 #![feature(const_float_classify)]
@@ -93,6 +94,7 @@
 #![feature(const_precise_live_drops)]
 #![feature(const_ptr_offset)]
 #![feature(const_ptr_offset_from)]
+#![feature(const_ptr_read)]
 #![feature(const_raw_ptr_comparison)]
 #![feature(const_raw_ptr_deref)]
 #![feature(const_slice_from_raw_parts)]
diff --git a/library/core/src/mem/maybe_uninit.rs b/library/core/src/mem/maybe_uninit.rs
index 57e0bb1499bde..b2a4d897eeded 100644
--- a/library/core/src/mem/maybe_uninit.rs
+++ b/library/core/src/mem/maybe_uninit.rs
@@ -575,8 +575,9 @@ impl<T> MaybeUninit<T> {
     /// // they both get dropped!
     /// ```
     #[unstable(feature = "maybe_uninit_extra", issue = "63567")]
+    #[rustc_const_unstable(feature = "maybe_uninit_extra", issue = "63567")]
     #[inline(always)]
-    pub unsafe fn assume_init_read(&self) -> T {
+    pub const unsafe fn assume_init_read(&self) -> T {
         // SAFETY: the caller must guarantee that `self` is initialized.
         // Reading from `self.as_ptr()` is safe since `self` should be initialized.
         unsafe {
diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs
index 8fd9ff768c4f4..e721d1243e88b 100644
--- a/library/core/src/ptr/const_ptr.rs
+++ b/library/core/src/ptr/const_ptr.rs
@@ -725,8 +725,9 @@ impl<T: ?Sized> *const T {
     ///
     /// [`ptr::read`]: crate::ptr::read()
     #[stable(feature = "pointer_methods", since = "1.26.0")]
+    #[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
     #[inline]
-    pub unsafe fn read(self) -> T
+    pub const unsafe fn read(self) -> T
     where
         T: Sized,
     {
@@ -763,8 +764,9 @@ impl<T: ?Sized> *const T {
     ///
     /// [`ptr::read_unaligned`]: crate::ptr::read_unaligned()
     #[stable(feature = "pointer_methods", since = "1.26.0")]
+    #[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
     #[inline]
-    pub unsafe fn read_unaligned(self) -> T
+    pub const unsafe fn read_unaligned(self) -> T
     where
         T: Sized,
     {
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index 27d49529a5ec2..807f114ea466c 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -685,7 +685,8 @@ pub unsafe fn replace<T>(dst: *mut T, mut src: T) -> T {
 /// [valid]: self#safety
 #[inline]
 #[stable(feature = "rust1", since = "1.0.0")]
-pub unsafe fn read<T>(src: *const T) -> T {
+#[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
+pub const unsafe fn read<T>(src: *const T) -> T {
     // `copy_nonoverlapping` takes care of debug_assert.
     let mut tmp = MaybeUninit::<T>::uninit();
     // SAFETY: the caller must guarantee that `src` is valid for reads.
@@ -784,7 +785,8 @@ pub unsafe fn read<T>(src: *const T) -> T {
 /// ```
 #[inline]
 #[stable(feature = "ptr_unaligned", since = "1.17.0")]
-pub unsafe fn read_unaligned<T>(src: *const T) -> T {
+#[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
+pub const unsafe fn read_unaligned<T>(src: *const T) -> T {
     // `copy_nonoverlapping` takes care of debug_assert.
     let mut tmp = MaybeUninit::<T>::uninit();
     // SAFETY: the caller must guarantee that `src` is valid for reads.
diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs
index 5f94c2393aef3..1788956894393 100644
--- a/library/core/src/ptr/mut_ptr.rs
+++ b/library/core/src/ptr/mut_ptr.rs
@@ -832,8 +832,9 @@ impl<T: ?Sized> *mut T {
     ///
     /// [`ptr::read`]: crate::ptr::read()
     #[stable(feature = "pointer_methods", since = "1.26.0")]
+    #[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
     #[inline]
-    pub unsafe fn read(self) -> T
+    pub const unsafe fn read(self) -> T
     where
         T: Sized,
     {
@@ -870,8 +871,9 @@ impl<T: ?Sized> *mut T {
     ///
     /// [`ptr::read_unaligned`]: crate::ptr::read_unaligned()
     #[stable(feature = "pointer_methods", since = "1.26.0")]
+    #[rustc_const_unstable(feature = "const_ptr_read", issue = "80377")]
     #[inline]
-    pub unsafe fn read_unaligned(self) -> T
+    pub const unsafe fn read_unaligned(self) -> T
     where
         T: Sized,
     {
diff --git a/library/core/tests/const_ptr.rs b/library/core/tests/const_ptr.rs
new file mode 100644
index 0000000000000..4acd059ab03df
--- /dev/null
+++ b/library/core/tests/const_ptr.rs
@@ -0,0 +1,51 @@
+// Aligned to two bytes
+const DATA: [u16; 2] = [u16::from_ne_bytes([0x01, 0x23]), u16::from_ne_bytes([0x45, 0x67])];
+
+const fn unaligned_ptr() -> *const u16 {
+    // Since DATA.as_ptr() is aligned to two bytes, adding 1 byte to that produces an unaligned *const u16
+    unsafe { (DATA.as_ptr() as *const u8).add(1) as *const u16 }
+}
+
+#[test]
+fn read() {
+    use core::ptr;
+
+    const FOO: i32 = unsafe { ptr::read(&42 as *const i32) };
+    assert_eq!(FOO, 42);
+
+    const ALIGNED: i32 = unsafe { ptr::read_unaligned(&42 as *const i32) };
+    assert_eq!(ALIGNED, 42);
+
+    const UNALIGNED_PTR: *const u16 = unaligned_ptr();
+
+    const UNALIGNED: u16 = unsafe { ptr::read_unaligned(UNALIGNED_PTR) };
+    assert_eq!(UNALIGNED, u16::from_ne_bytes([0x23, 0x45]));
+}
+
+#[test]
+fn const_ptr_read() {
+    const FOO: i32 = unsafe { (&42 as *const i32).read() };
+    assert_eq!(FOO, 42);
+
+    const ALIGNED: i32 = unsafe { (&42 as *const i32).read_unaligned() };
+    assert_eq!(ALIGNED, 42);
+
+    const UNALIGNED_PTR: *const u16 = unaligned_ptr();
+
+    const UNALIGNED: u16 = unsafe { UNALIGNED_PTR.read_unaligned() };
+    assert_eq!(UNALIGNED, u16::from_ne_bytes([0x23, 0x45]));
+}
+
+#[test]
+fn mut_ptr_read() {
+    const FOO: i32 = unsafe { (&42 as *const i32 as *mut i32).read() };
+    assert_eq!(FOO, 42);
+
+    const ALIGNED: i32 = unsafe { (&42 as *const i32 as *mut i32).read_unaligned() };
+    assert_eq!(ALIGNED, 42);
+
+    const UNALIGNED_PTR: *mut u16 = unaligned_ptr() as *mut u16;
+
+    const UNALIGNED: u16 = unsafe { UNALIGNED_PTR.read_unaligned() };
+    assert_eq!(UNALIGNED, u16::from_ne_bytes([0x23, 0x45]));
+}
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index 2828235c3e38d..9e8ec7060216b 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -13,6 +13,8 @@
 #![feature(const_assume)]
 #![feature(const_cell_into_inner)]
 #![feature(const_maybe_uninit_assume_init)]
+#![feature(const_ptr_read)]
+#![feature(const_ptr_offset)]
 #![feature(core_intrinsics)]
 #![feature(core_private_bignum)]
 #![feature(core_private_diy_float)]
@@ -34,6 +36,7 @@
 #![feature(raw)]
 #![feature(sort_internals)]
 #![feature(slice_partition_at_index)]
+#![feature(maybe_uninit_extra)]
 #![feature(maybe_uninit_write_slice)]
 #![feature(min_specialization)]
 #![feature(step_trait)]
@@ -82,6 +85,10 @@ mod cell;
 mod char;
 mod clone;
 mod cmp;
+
+#[cfg(not(bootstrap))]
+mod const_ptr;
+
 mod fmt;
 mod hash;
 mod intrinsics;
diff --git a/library/core/tests/mem.rs b/library/core/tests/mem.rs
index 5d0fedd4d9ccc..a09a2e91804b1 100644
--- a/library/core/tests/mem.rs
+++ b/library/core/tests/mem.rs
@@ -267,3 +267,10 @@ fn uninit_write_slice_cloned_no_drop() {
 
     forget(src);
 }
+
+#[test]
+#[cfg(not(bootstrap))]
+fn uninit_const_assume_init_read() {
+    const FOO: u32 = unsafe { MaybeUninit::new(42).assume_init_read() };
+    assert_eq!(FOO, 42);
+}
diff --git a/src/test/ui/const-ptr/out_of_bounds_read.rs b/src/test/ui/const-ptr/out_of_bounds_read.rs
new file mode 100644
index 0000000000000..183aa9e51228c
--- /dev/null
+++ b/src/test/ui/const-ptr/out_of_bounds_read.rs
@@ -0,0 +1,16 @@
+// error-pattern: any use of this value will cause an error
+
+#![feature(const_ptr_read)]
+#![feature(const_ptr_offset)]
+
+fn main() {
+    use std::ptr;
+
+    const DATA: [u32; 1] = [42];
+
+    const PAST_END_PTR: *const u32 = unsafe { DATA.as_ptr().add(1) };
+
+    const _READ: u32 = unsafe { ptr::read(PAST_END_PTR) };
+    const _CONST_READ: u32 = unsafe { PAST_END_PTR.read() };
+    const _MUT_READ: u32 = unsafe { (PAST_END_PTR as *mut u32).read() };
+}
diff --git a/src/test/ui/const-ptr/out_of_bounds_read.stderr b/src/test/ui/const-ptr/out_of_bounds_read.stderr
new file mode 100644
index 0000000000000..ca65a079947e0
--- /dev/null
+++ b/src/test/ui/const-ptr/out_of_bounds_read.stderr
@@ -0,0 +1,54 @@
+error: any use of this value will cause an error
+  --> $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |
+LL |     unsafe { copy_nonoverlapping(src, dst, count) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |              |
+   |              memory access failed: pointer must be in-bounds at offset 8, but is outside bounds of alloc6 which has size 4
+   |              inside `copy_nonoverlapping::<u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |              inside `std::ptr::read::<u32>` at $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+   |              inside `_READ` at $DIR/out_of_bounds_read.rs:13:33
+   | 
+  ::: $DIR/out_of_bounds_read.rs:13:5
+   |
+LL |     const _READ: u32 = unsafe { ptr::read(PAST_END_PTR) };
+   |     ------------------------------------------------------
+   |
+   = note: `#[deny(const_err)]` on by default
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |
+LL |     unsafe { copy_nonoverlapping(src, dst, count) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |              |
+   |              memory access failed: pointer must be in-bounds at offset 8, but is outside bounds of alloc6 which has size 4
+   |              inside `copy_nonoverlapping::<u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |              inside `std::ptr::read::<u32>` at $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+   |              inside `ptr::const_ptr::<impl *const u32>::read` at $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
+   |              inside `_CONST_READ` at $DIR/out_of_bounds_read.rs:14:39
+   | 
+  ::: $DIR/out_of_bounds_read.rs:14:5
+   |
+LL |     const _CONST_READ: u32 = unsafe { PAST_END_PTR.read() };
+   |     --------------------------------------------------------
+
+error: any use of this value will cause an error
+  --> $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |
+LL |     unsafe { copy_nonoverlapping(src, dst, count) }
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |              |
+   |              memory access failed: pointer must be in-bounds at offset 8, but is outside bounds of alloc6 which has size 4
+   |              inside `copy_nonoverlapping::<u32>` at $SRC_DIR/core/src/intrinsics.rs:LL:COL
+   |              inside `std::ptr::read::<u32>` at $SRC_DIR/core/src/ptr/mod.rs:LL:COL
+   |              inside `ptr::mut_ptr::<impl *mut u32>::read` at $SRC_DIR/core/src/ptr/mut_ptr.rs:LL:COL
+   |              inside `_MUT_READ` at $DIR/out_of_bounds_read.rs:15:37
+   | 
+  ::: $DIR/out_of_bounds_read.rs:15:5
+   |
+LL |     const _MUT_READ: u32 = unsafe { (PAST_END_PTR as *mut u32).read() };
+   |     --------------------------------------------------------------------
+
+error: aborting due to 3 previous errors
+