about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-07-09 09:16:27 +0000
committerbors <bors@rust-lang.org>2021-07-09 09:16:27 +0000
commitee86f96ba176f598d64dc9f3bb7e074d5b8b86b6 (patch)
tree31e632ae31b54ae25e9323bb51fe507e31d140ed
parent95fb1315217976ff4c268bb03c9b4132f0dfa9fd (diff)
parentd0644947a3b093bfc09452ecd14291cea5dd8f14 (diff)
downloadrust-ee86f96ba176f598d64dc9f3bb7e074d5b8b86b6.tar.gz
rust-ee86f96ba176f598d64dc9f3bb7e074d5b8b86b6.zip
Auto merge of #85828 - scottmcm:raw-eq, r=oli-obk
Stop generating `alloca`s & `memcmp` for simple short array equality

Example:
```rust
pub fn demo(x: [u16; 6], y: [u16; 6]) -> bool { x == y }
```

Before:
```llvm
define zeroext i1 `@_ZN10playground4demo17h48537f7eac23948fE(i96` %0, i96 %1) unnamed_addr #0 {
start:
  %y = alloca [6 x i16], align 8
  %x = alloca [6 x i16], align 8
  %.0..sroa_cast = bitcast [6 x i16]* %x to i96*
  store i96 %0, i96* %.0..sroa_cast, align 8
  %.0..sroa_cast3 = bitcast [6 x i16]* %y to i96*
  store i96 %1, i96* %.0..sroa_cast3, align 8
  %_11.i.i.i = bitcast [6 x i16]* %x to i8*
  %_14.i.i.i = bitcast [6 x i16]* %y to i8*
  %bcmp.i.i.i = call i32 `@bcmp(i8*` nonnull dereferenceable(12) %_11.i.i.i, i8* nonnull dereferenceable(12) %_14.i.i.i, i64 12) #2, !alias.scope !2
  %2 = icmp eq i32 %bcmp.i.i.i, 0
  ret i1 %2
}
```
```x86
playground::demo: # `@playground::demo`
	sub	rsp, 32
	mov	qword ptr [rsp], rdi
	mov	dword ptr [rsp + 8], esi
	mov	qword ptr [rsp + 16], rdx
	mov	dword ptr [rsp + 24], ecx
	xor	rdi, rdx
	xor	esi, ecx
	or	rsi, rdi
	sete	al
	add	rsp, 32
	ret
```

After:
```llvm
define zeroext i1 `@_ZN4mini4demo17h7a8994aaa314c981E(i96` %0, i96 %1) unnamed_addr #0 {
start:
  %2 = icmp eq i96 %0, %1
  ret i1 %2
}
```
```x86
_ZN4mini4demo17h7a8994aaa314c981E:
	xor	rcx, r8
	xor	edx, r9d
	or	rdx, rcx
	sete	al
	ret
```
-rw-r--r--compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs34
-rw-r--r--compiler/rustc_codegen_cranelift/src/value_and_place.rs4
-rw-r--r--compiler/rustc_codegen_llvm/src/context.rs5
-rw-r--r--compiler/rustc_codegen_llvm/src/intrinsic.rs38
-rw-r--r--compiler/rustc_mir/src/interpret/intrinsics.rs19
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_typeck/src/check/intrinsic.rs7
-rw-r--r--library/core/src/array/equality.rs160
-rw-r--r--library/core/src/array/mod.rs113
-rw-r--r--library/core/src/intrinsics.rs20
-rw-r--r--src/test/codegen/array-equality.rs57
-rw-r--r--src/test/codegen/slice-ref-equality.rs19
-rw-r--r--src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs11
-rw-r--r--src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.stderr9
-rw-r--r--src/test/ui/intrinsics/intrinsic-raw_eq-const.rs27
15 files changed, 410 insertions, 114 deletions
diff --git a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
index 52896fc7127..3979886e10c 100644
--- a/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
+++ b/compiler/rustc_codegen_cranelift/src/intrinsics/mod.rs
@@ -1115,6 +1115,40 @@ pub(crate) fn codegen_intrinsic_call<'tcx>(
             );
             ret.write_cvalue(fx, CValue::by_val(res, ret.layout()));
         };
+
+        raw_eq, <T>(v lhs_ref, v rhs_ref) {
+            fn type_by_size(size: Size) -> Option<Type> {
+                Type::int(size.bits().try_into().ok()?)
+            }
+
+            let size = fx.layout_of(T).layout.size;
+            let is_eq_value =
+                if size == Size::ZERO {
+                    // No bytes means they're trivially equal
+                    fx.bcx.ins().iconst(types::I8, 1)
+                } else if let Some(clty) = type_by_size(size) {
+                    // Can't use `trusted` for these loads; they could be unaligned.
+                    let mut flags = MemFlags::new();
+                    flags.set_notrap();
+                    let lhs_val = fx.bcx.ins().load(clty, flags, lhs_ref, 0);
+                    let rhs_val = fx.bcx.ins().load(clty, flags, rhs_ref, 0);
+                    let eq = fx.bcx.ins().icmp(IntCC::Equal, lhs_val, rhs_val);
+                    fx.bcx.ins().bint(types::I8, eq)
+                } else {
+                    // Just call `memcmp` (like slices do in core) when the
+                    // size is too large or it's not a power-of-two.
+                    let ptr_ty = pointer_ty(fx.tcx);
+                    let signed_bytes = i64::try_from(size.bytes()).unwrap();
+                    let bytes_val = fx.bcx.ins().iconst(ptr_ty, signed_bytes);
+                    let params = vec![AbiParam::new(ptr_ty); 3];
+                    let returns = vec![AbiParam::new(types::I32)];
+                    let args = &[lhs_ref, rhs_ref, bytes_val];
+                    let cmp = fx.lib_call("memcmp", params, returns, args)[0];
+                    let eq = fx.bcx.ins().icmp_imm(IntCC::Equal, cmp, 0);
+                    fx.bcx.ins().bint(types::I8, eq)
+                };
+            ret.write_cvalue(fx, CValue::by_val(is_eq_value, ret.layout()));
+        };
     }
 
     if let Some((_, dest)) = destination {
diff --git a/compiler/rustc_codegen_cranelift/src/value_and_place.rs b/compiler/rustc_codegen_cranelift/src/value_and_place.rs
index 171f39805f8..ae8ccc626b4 100644
--- a/compiler/rustc_codegen_cranelift/src/value_and_place.rs
+++ b/compiler/rustc_codegen_cranelift/src/value_and_place.rs
@@ -453,6 +453,10 @@ impl<'tcx> CPlace<'tcx> {
                     ptr.store(fx, data, MemFlags::trusted());
                     ptr.load(fx, dst_ty, MemFlags::trusted())
                 }
+
+                // `CValue`s should never contain SSA-only types, so if you ended
+                // up here having seen an error like `B1 -> I8`, then before
+                // calling `write_cvalue` you need to add a `bint` instruction.
                 _ => unreachable!("write_cvalue_transmute: {:?} -> {:?}", src_ty, dst_ty),
             };
             //fx.bcx.set_val_label(data, cranelift_codegen::ir::ValueLabel::new(var.index()));
diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs
index f662887abf8..d1aecd32e2f 100644
--- a/compiler/rustc_codegen_llvm/src/context.rs
+++ b/compiler/rustc_codegen_llvm/src/context.rs
@@ -500,6 +500,7 @@ impl CodegenCx<'b, 'tcx> {
         let t_i32 = self.type_i32();
         let t_i64 = self.type_i64();
         let t_i128 = self.type_i128();
+        let t_isize = self.type_isize();
         let t_f32 = self.type_f32();
         let t_f64 = self.type_f64();
 
@@ -712,6 +713,10 @@ impl CodegenCx<'b, 'tcx> {
         ifn!("llvm.assume", fn(i1) -> void);
         ifn!("llvm.prefetch", fn(i8p, t_i32, t_i32, t_i32) -> void);
 
+        // This isn't an "LLVM intrinsic", but LLVM's optimization passes
+        // recognize it like one and we assume it exists in `core::slice::cmp`
+        ifn!("memcmp", fn(i8p, i8p, t_isize) -> t_i32);
+
         // variadic intrinsics
         ifn!("llvm.va_start", fn(i8p) -> void);
         ifn!("llvm.va_end", fn(i8p) -> void);
diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs
index 1fb201eda6b..9a968659e2f 100644
--- a/compiler/rustc_codegen_llvm/src/intrinsic.rs
+++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs
@@ -296,6 +296,44 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
                 }
             }
 
+            sym::raw_eq => {
+                use abi::Abi::*;
+                let tp_ty = substs.type_at(0);
+                let layout = self.layout_of(tp_ty).layout;
+                let use_integer_compare = match layout.abi {
+                    Scalar(_) | ScalarPair(_, _) => true,
+                    Uninhabited | Vector { .. } => false,
+                    Aggregate { .. } => {
+                        // For rusty ABIs, small aggregates are actually passed
+                        // as `RegKind::Integer` (see `FnAbi::adjust_for_abi`),
+                        // so we re-use that same threshold here.
+                        layout.size <= self.data_layout().pointer_size * 2
+                    }
+                };
+
+                let a = args[0].immediate();
+                let b = args[1].immediate();
+                if layout.size.bytes() == 0 {
+                    self.const_bool(true)
+                } else if use_integer_compare {
+                    let integer_ty = self.type_ix(layout.size.bits());
+                    let ptr_ty = self.type_ptr_to(integer_ty);
+                    let a_ptr = self.bitcast(a, ptr_ty);
+                    let a_val = self.load(a_ptr, layout.align.abi);
+                    let b_ptr = self.bitcast(b, ptr_ty);
+                    let b_val = self.load(b_ptr, layout.align.abi);
+                    self.icmp(IntPredicate::IntEQ, a_val, b_val)
+                } else {
+                    let i8p_ty = self.type_i8p();
+                    let a_ptr = self.bitcast(a, i8p_ty);
+                    let b_ptr = self.bitcast(b, i8p_ty);
+                    let n = self.const_usize(layout.size.bytes());
+                    let llfn = self.get_intrinsic("memcmp");
+                    let cmp = self.call(llfn, &[a_ptr, b_ptr, n], None);
+                    self.icmp(IntPredicate::IntEQ, cmp, self.const_i32(0))
+                }
+            }
+
             _ if name_str.starts_with("simd_") => {
                 match generic_simd_intrinsic(self, name, callee_ty, args, ret_ty, llret_ty, span) {
                     Ok(llval) => llval,
diff --git a/compiler/rustc_mir/src/interpret/intrinsics.rs b/compiler/rustc_mir/src/interpret/intrinsics.rs
index 4e4166dad50..ad9cf3e7d2f 100644
--- a/compiler/rustc_mir/src/interpret/intrinsics.rs
+++ b/compiler/rustc_mir/src/interpret/intrinsics.rs
@@ -472,6 +472,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                     throw_ub_format!("`assume` intrinsic called with `false`");
                 }
             }
+            sym::raw_eq => {
+                let result = self.raw_eq_intrinsic(&args[0], &args[1])?;
+                self.write_scalar(result, dest)?;
+            }
             _ => return Ok(false),
         }
 
@@ -559,4 +563,19 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
 
         self.memory.copy(src, align, dst, align, size, nonoverlapping)
     }
+
+    pub(crate) fn raw_eq_intrinsic(
+        &mut self,
+        lhs: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::PointerTag>,
+        rhs: &OpTy<'tcx, <M as Machine<'mir, 'tcx>>::PointerTag>,
+    ) -> InterpResult<'tcx, Scalar<M::PointerTag>> {
+        let layout = self.layout_of(lhs.layout.ty.builtin_deref(true).unwrap().ty)?;
+        assert!(!layout.is_unsized());
+
+        let lhs = self.read_scalar(lhs)?.check_init()?;
+        let rhs = self.read_scalar(rhs)?.check_init()?;
+        let lhs_bytes = self.memory.read_bytes(lhs, layout.size)?;
+        let rhs_bytes = self.memory.read_bytes(rhs, layout.size)?;
+        Ok(Scalar::from_bool(lhs_bytes == rhs_bytes))
+    }
 }
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 5e9a2904b7c..9051c9d69b5 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -934,6 +934,7 @@ symbols! {
         quote,
         range_inclusive_new,
         raw_dylib,
+        raw_eq,
         raw_identifiers,
         raw_ref_op,
         re_rebalance_coherence,
diff --git a/compiler/rustc_typeck/src/check/intrinsic.rs b/compiler/rustc_typeck/src/check/intrinsic.rs
index 882d5d54b7c..6661df21ed9 100644
--- a/compiler/rustc_typeck/src/check/intrinsic.rs
+++ b/compiler/rustc_typeck/src/check/intrinsic.rs
@@ -380,6 +380,13 @@ pub fn check_intrinsic_type(tcx: TyCtxt<'_>, it: &hir::ForeignItem<'_>) {
 
             sym::nontemporal_store => (1, vec![tcx.mk_mut_ptr(param(0)), param(0)], tcx.mk_unit()),
 
+            sym::raw_eq => {
+                let br = ty::BoundRegion { var: ty::BoundVar::from_u32(0), kind: ty::BrAnon(0) };
+                let param_ty =
+                    tcx.mk_imm_ref(tcx.mk_region(ty::ReLateBound(ty::INNERMOST, br)), param(0));
+                (1, vec![param_ty; 2], tcx.types.bool)
+            }
+
             other => {
                 tcx.sess.emit_err(UnrecognizedIntrinsicFunction { span: it.span, name: other });
                 return;
diff --git a/library/core/src/array/equality.rs b/library/core/src/array/equality.rs
new file mode 100644
index 00000000000..6d66b9e2f27
--- /dev/null
+++ b/library/core/src/array/equality.rs
@@ -0,0 +1,160 @@
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
+where
+    A: PartialEq<B>,
+{
+    #[inline]
+    fn eq(&self, other: &[B; N]) -> bool {
+        SpecArrayEq::spec_eq(self, other)
+    }
+    #[inline]
+    fn ne(&self, other: &[B; N]) -> bool {
+        SpecArrayEq::spec_ne(self, other)
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
+where
+    A: PartialEq<B>,
+{
+    #[inline]
+    fn eq(&self, other: &[B]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &[B]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
+where
+    B: PartialEq<A>,
+{
+    #[inline]
+    fn eq(&self, other: &[A; N]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &[A; N]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
+where
+    A: PartialEq<B>,
+{
+    #[inline]
+    fn eq(&self, other: &&[B]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &&[B]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
+where
+    B: PartialEq<A>,
+{
+    #[inline]
+    fn eq(&self, other: &[A; N]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &[A; N]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
+where
+    A: PartialEq<B>,
+{
+    #[inline]
+    fn eq(&self, other: &&mut [B]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &&mut [B]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
+where
+    B: PartialEq<A>,
+{
+    #[inline]
+    fn eq(&self, other: &[A; N]) -> bool {
+        self[..] == other[..]
+    }
+    #[inline]
+    fn ne(&self, other: &[A; N]) -> bool {
+        self[..] != other[..]
+    }
+}
+
+// NOTE: some less important impls are omitted to reduce code bloat
+// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
+// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
+
+#[stable(feature = "rust1", since = "1.0.0")]
+impl<T: Eq, const N: usize> Eq for [T; N] {}
+
+trait SpecArrayEq<Other, const N: usize>: Sized {
+    fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool;
+    fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool;
+}
+
+impl<T: PartialEq<Other>, Other, const N: usize> SpecArrayEq<Other, N> for T {
+    default fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool {
+        a[..] == b[..]
+    }
+    default fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool {
+        a[..] != b[..]
+    }
+}
+
+impl<T: PartialEq<U> + IsRawEqComparable<U>, U, const N: usize> SpecArrayEq<U, N> for T {
+    #[cfg(bootstrap)]
+    fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
+        a[..] == b[..]
+    }
+    #[cfg(not(bootstrap))]
+    fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
+        // SAFETY: This is why `IsRawEqComparable` is an `unsafe trait`.
+        unsafe {
+            let b = &*b.as_ptr().cast::<[T; N]>();
+            crate::intrinsics::raw_eq(a, b)
+        }
+    }
+    fn spec_ne(a: &[T; N], b: &[U; N]) -> bool {
+        !Self::spec_eq(a, b)
+    }
+}
+
+/// `U` exists on here mostly because `min_specialization` didn't let me
+/// repeat the `T` type parameter in the above specialization, so instead
+/// the `T == U` constraint comes from the impls on this.
+/// # Safety
+/// - Neither `Self` nor `U` has any padding.
+/// - `Self` and `U` have the same layout.
+/// - `Self: PartialEq<U>` is byte-wise (this means no floats, among other things)
+#[rustc_specialization_trait]
+unsafe trait IsRawEqComparable<U> {}
+
+macro_rules! is_raw_comparable {
+    ($($t:ty),+) => {$(
+        unsafe impl IsRawEqComparable<$t> for $t {}
+    )+};
+}
+is_raw_comparable!(bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
diff --git a/library/core/src/array/mod.rs b/library/core/src/array/mod.rs
index 030b42a53d0..32d344010aa 100644
--- a/library/core/src/array/mod.rs
+++ b/library/core/src/array/mod.rs
@@ -14,6 +14,7 @@ use crate::mem::{self, MaybeUninit};
 use crate::ops::{Index, IndexMut};
 use crate::slice::{Iter, IterMut};
 
+mod equality;
 mod iter;
 
 #[stable(feature = "array_value_iter", since = "1.51.0")]
@@ -231,118 +232,6 @@ where
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
-where
-    A: PartialEq<B>,
-{
-    #[inline]
-    fn eq(&self, other: &[B; N]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &[B; N]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
-where
-    A: PartialEq<B>,
-{
-    #[inline]
-    fn eq(&self, other: &[B]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &[B]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
-where
-    B: PartialEq<A>,
-{
-    #[inline]
-    fn eq(&self, other: &[A; N]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &[A; N]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
-where
-    A: PartialEq<B>,
-{
-    #[inline]
-    fn eq(&self, other: &&[B]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &&[B]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
-where
-    B: PartialEq<A>,
-{
-    #[inline]
-    fn eq(&self, other: &[A; N]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &[A; N]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
-where
-    A: PartialEq<B>,
-{
-    #[inline]
-    fn eq(&self, other: &&mut [B]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &&mut [B]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
-where
-    B: PartialEq<A>,
-{
-    #[inline]
-    fn eq(&self, other: &[A; N]) -> bool {
-        self[..] == other[..]
-    }
-    #[inline]
-    fn ne(&self, other: &[A; N]) -> bool {
-        self[..] != other[..]
-    }
-}
-
-// NOTE: some less important impls are omitted to reduce code bloat
-// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
-// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
-
-#[stable(feature = "rust1", since = "1.0.0")]
-impl<T: Eq, const N: usize> Eq for [T; N] {}
-
-#[stable(feature = "rust1", since = "1.0.0")]
 impl<T: PartialOrd, const N: usize> PartialOrd for [T; N] {
     #[inline]
     fn partial_cmp(&self, other: &[T; N]) -> Option<Ordering> {
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index c5a4bbd3208..238f00e41b3 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -1913,6 +1913,26 @@ extern "rust-intrinsic" {
     /// Allocate at compile time. Should not be called at runtime.
     #[rustc_const_unstable(feature = "const_heap", issue = "79597")]
     pub fn const_allocate(size: usize, align: usize) -> *mut u8;
+
+    /// Determines whether the raw bytes of the two values are equal.
+    ///
+    /// The is particularly handy for arrays, since it allows things like just
+    /// comparing `i96`s instead of forcing `alloca`s for `[6 x i16]`.
+    ///
+    /// Above some backend-decided threshold this will emit calls to `memcmp`,
+    /// like slice equality does, instead of causing massive code size.
+    ///
+    /// # Safety
+    ///
+    /// It's UB to call this if any of the *bytes* in `*a` or `*b` are uninitialized.
+    /// Note that this is a stricter criterion than just the *values* being
+    /// fully-initialized: if `T` has padding, it's UB to call this intrinsic.
+    ///
+    /// (The implementation is allowed to branch on the results of comparisons,
+    /// which is UB if any of their inputs are `undef`.)
+    #[cfg(not(bootstrap))]
+    #[rustc_const_unstable(feature = "const_intrinsic_raw_eq", issue = "none")]
+    pub fn raw_eq<T>(a: &T, b: &T) -> bool;
 }
 
 // Some functions are defined here because they accidentally got made
diff --git a/src/test/codegen/array-equality.rs b/src/test/codegen/array-equality.rs
new file mode 100644
index 00000000000..4b60fa4b0bf
--- /dev/null
+++ b/src/test/codegen/array-equality.rs
@@ -0,0 +1,57 @@
+// compile-flags: -O
+// only-x86_64
+
+#![crate_type = "lib"]
+
+// CHECK-LABEL: @array_eq_value
+#[no_mangle]
+pub fn array_eq_value(a: [u16; 6], b: [u16; 6]) -> bool {
+    // CHECK-NEXT: start:
+    // CHECK-NEXT: %2 = icmp eq i96 %0, %1
+    // CHECK-NEXT: ret i1 %2
+    a == b
+}
+
+// CHECK-LABEL: @array_eq_ref
+#[no_mangle]
+pub fn array_eq_ref(a: &[u16; 6], b: &[u16; 6]) -> bool {
+    // CHECK: start:
+    // CHECK: load i96, i96* %{{.+}}, align 2
+    // CHECK: load i96, i96* %{{.+}}, align 2
+    // CHECK: icmp eq i96
+    // CHECK-NEXT: ret
+    a == b
+}
+
+// CHECK-LABEL: @array_eq_value_still_passed_by_pointer
+#[no_mangle]
+pub fn array_eq_value_still_passed_by_pointer(a: [u16; 9], b: [u16; 9]) -> bool {
+    // CHECK-NEXT: start:
+    // CHECK-NEXT: bitcast
+    // CHECK-NEXT: bitcast
+    // CHECK-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(i8* nonnull dereferenceable(18) %{{.+}}, i8* nonnull dereferenceable(18) %{{.+}}, i64 18)
+    // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
+    // CHECK-NEXT: ret i1 %[[EQ]]
+    a == b
+}
+
+// CHECK-LABEL: @array_eq_long
+#[no_mangle]
+pub fn array_eq_long(a: &[u16; 1234], b: &[u16; 1234]) -> bool {
+    // CHECK-NEXT: start:
+    // CHECK-NEXT: bitcast
+    // CHECK-NEXT: bitcast
+    // CHECK-NEXT: %[[CMP:.+]] = tail call i32 @{{bcmp|memcmp}}(i8* nonnull dereferenceable(2468) %{{.+}}, i8* nonnull dereferenceable(2468) %{{.+}}, i64 2468)
+    // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[CMP]], 0
+    // CHECK-NEXT: ret i1 %[[EQ]]
+    a == b
+}
+
+// CHECK-LABEL: @array_eq_zero(i128 %0)
+#[no_mangle]
+pub fn array_eq_zero(x: [u16; 8]) -> bool {
+    // CHECK-NEXT: start:
+    // CHECK-NEXT: %[[EQ:.+]] = icmp eq i128 %0, 0
+    // CHECK-NEXT: ret i1 %[[EQ]]
+    x == [0; 8]
+}
diff --git a/src/test/codegen/slice-ref-equality.rs b/src/test/codegen/slice-ref-equality.rs
index acc7879e7b1..1f99ac7342b 100644
--- a/src/test/codegen/slice-ref-equality.rs
+++ b/src/test/codegen/slice-ref-equality.rs
@@ -2,15 +2,30 @@
 
 #![crate_type = "lib"]
 
-// #71602: check that slice equality just generates a single bcmp
+// #71602 reported a simple array comparison just generating a loop.
+// This was originally fixed by ensuring it generates a single bcmp,
+// but we now generate it as a load instead. `is_zero_slice` was
+// tweaked to still test the case of comparison against a slice,
+// and `is_zero_array` tests the new array-specific behaviour.
 
 // CHECK-LABEL: @is_zero_slice
 #[no_mangle]
 pub fn is_zero_slice(data: &[u8; 4]) -> bool {
-    // CHECK: start:
+    // CHECK: :
     // CHECK-NEXT: %{{.+}} = getelementptr {{.+}}
     // CHECK-NEXT: %[[BCMP:.+]] = tail call i32 @{{bcmp|memcmp}}({{.+}})
     // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[BCMP]], 0
     // CHECK-NEXT: ret i1 %[[EQ]]
+    &data[..] == [0; 4]
+}
+
+// CHECK-LABEL: @is_zero_array
+#[no_mangle]
+pub fn is_zero_array(data: &[u8; 4]) -> bool {
+    // CHECK: start:
+    // CHECK-NEXT: %[[PTR:.+]] = bitcast [4 x i8]* {{.+}} to i32*
+    // CHECK-NEXT: %[[LOAD:.+]] = load i32, i32* %[[PTR]], align 1
+    // CHECK-NEXT: %[[EQ:.+]] = icmp eq i32 %[[LOAD]], 0
+    // CHECK-NEXT: ret i1 %[[EQ]]
     *data == [0; 4]
 }
diff --git a/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs b/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs
new file mode 100644
index 00000000000..a205a8730a0
--- /dev/null
+++ b/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.rs
@@ -0,0 +1,11 @@
+#![feature(core_intrinsics)]
+#![feature(const_intrinsic_raw_eq)]
+#![deny(const_err)]
+
+const BAD_RAW_EQ_CALL: bool = unsafe {
+    std::intrinsics::raw_eq(&(1_u8, 2_u16), &(1_u8, 2_u16))
+//~^ ERROR evaluation of constant value failed
+};
+
+pub fn main() {
+}
diff --git a/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.stderr b/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.stderr
new file mode 100644
index 00000000000..3a1b0900127
--- /dev/null
+++ b/src/test/ui/intrinsics/intrinsic-raw_eq-const-padding.stderr
@@ -0,0 +1,9 @@
+error[E0080]: evaluation of constant value failed
+  --> $DIR/intrinsic-raw_eq-const-padding.rs:6:5
+   |
+LL |     std::intrinsics::raw_eq(&(1_u8, 2_u16), &(1_u8, 2_u16))
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading 4 bytes of memory starting at alloc2, but 1 byte is uninitialized starting at alloc2+0x1, and this operation requires initialized memory
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0080`.
diff --git a/src/test/ui/intrinsics/intrinsic-raw_eq-const.rs b/src/test/ui/intrinsics/intrinsic-raw_eq-const.rs
new file mode 100644
index 00000000000..8ea95467302
--- /dev/null
+++ b/src/test/ui/intrinsics/intrinsic-raw_eq-const.rs
@@ -0,0 +1,27 @@
+// run-pass
+
+#![feature(core_intrinsics)]
+#![feature(const_intrinsic_raw_eq)]
+#![deny(const_err)]
+
+pub fn main() {
+    use std::intrinsics::raw_eq;
+
+    const RAW_EQ_I32_TRUE: bool = unsafe { raw_eq(&42_i32, &42) };
+    assert!(RAW_EQ_I32_TRUE);
+
+    const RAW_EQ_I32_FALSE: bool = unsafe { raw_eq(&4_i32, &2) };
+    assert!(!RAW_EQ_I32_FALSE);
+
+    const RAW_EQ_CHAR_TRUE: bool = unsafe { raw_eq(&'a', &'a') };
+    assert!(RAW_EQ_CHAR_TRUE);
+
+    const RAW_EQ_CHAR_FALSE: bool = unsafe { raw_eq(&'a', &'A') };
+    assert!(!RAW_EQ_CHAR_FALSE);
+
+    const RAW_EQ_ARRAY_TRUE: bool = unsafe { raw_eq(&[13_u8, 42], &[13, 42]) };
+    assert!(RAW_EQ_ARRAY_TRUE);
+
+    const RAW_EQ_ARRAY_FALSE: bool = unsafe { raw_eq(&[13_u8, 42], &[42, 13]) };
+    assert!(!RAW_EQ_ARRAY_FALSE);
+}