Skip to content

Commit 5123d5d

Browse files
committed
Auto merge of rust-lang#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 ```
2 parents 891ff4b + e01077a commit 5123d5d

File tree

3 files changed

+181
-112
lines changed

3 files changed

+181
-112
lines changed

core/src/array/equality.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#[stable(feature = "rust1", since = "1.0.0")]
2+
impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
3+
where
4+
A: PartialEq<B>,
5+
{
6+
#[inline]
7+
fn eq(&self, other: &[B; N]) -> bool {
8+
SpecArrayEq::spec_eq(self, other)
9+
}
10+
#[inline]
11+
fn ne(&self, other: &[B; N]) -> bool {
12+
SpecArrayEq::spec_ne(self, other)
13+
}
14+
}
15+
16+
#[stable(feature = "rust1", since = "1.0.0")]
17+
impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
18+
where
19+
A: PartialEq<B>,
20+
{
21+
#[inline]
22+
fn eq(&self, other: &[B]) -> bool {
23+
self[..] == other[..]
24+
}
25+
#[inline]
26+
fn ne(&self, other: &[B]) -> bool {
27+
self[..] != other[..]
28+
}
29+
}
30+
31+
#[stable(feature = "rust1", since = "1.0.0")]
32+
impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
33+
where
34+
B: PartialEq<A>,
35+
{
36+
#[inline]
37+
fn eq(&self, other: &[A; N]) -> bool {
38+
self[..] == other[..]
39+
}
40+
#[inline]
41+
fn ne(&self, other: &[A; N]) -> bool {
42+
self[..] != other[..]
43+
}
44+
}
45+
46+
#[stable(feature = "rust1", since = "1.0.0")]
47+
impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
48+
where
49+
A: PartialEq<B>,
50+
{
51+
#[inline]
52+
fn eq(&self, other: &&[B]) -> bool {
53+
self[..] == other[..]
54+
}
55+
#[inline]
56+
fn ne(&self, other: &&[B]) -> bool {
57+
self[..] != other[..]
58+
}
59+
}
60+
61+
#[stable(feature = "rust1", since = "1.0.0")]
62+
impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
63+
where
64+
B: PartialEq<A>,
65+
{
66+
#[inline]
67+
fn eq(&self, other: &[A; N]) -> bool {
68+
self[..] == other[..]
69+
}
70+
#[inline]
71+
fn ne(&self, other: &[A; N]) -> bool {
72+
self[..] != other[..]
73+
}
74+
}
75+
76+
#[stable(feature = "rust1", since = "1.0.0")]
77+
impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
78+
where
79+
A: PartialEq<B>,
80+
{
81+
#[inline]
82+
fn eq(&self, other: &&mut [B]) -> bool {
83+
self[..] == other[..]
84+
}
85+
#[inline]
86+
fn ne(&self, other: &&mut [B]) -> bool {
87+
self[..] != other[..]
88+
}
89+
}
90+
91+
#[stable(feature = "rust1", since = "1.0.0")]
92+
impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
93+
where
94+
B: PartialEq<A>,
95+
{
96+
#[inline]
97+
fn eq(&self, other: &[A; N]) -> bool {
98+
self[..] == other[..]
99+
}
100+
#[inline]
101+
fn ne(&self, other: &[A; N]) -> bool {
102+
self[..] != other[..]
103+
}
104+
}
105+
106+
// NOTE: some less important impls are omitted to reduce code bloat
107+
// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
108+
// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
109+
110+
#[stable(feature = "rust1", since = "1.0.0")]
111+
impl<T: Eq, const N: usize> Eq for [T; N] {}
112+
113+
trait SpecArrayEq<Other, const N: usize>: Sized {
114+
fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool;
115+
fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool;
116+
}
117+
118+
impl<T: PartialEq<Other>, Other, const N: usize> SpecArrayEq<Other, N> for T {
119+
default fn spec_eq(a: &[Self; N], b: &[Other; N]) -> bool {
120+
a[..] == b[..]
121+
}
122+
default fn spec_ne(a: &[Self; N], b: &[Other; N]) -> bool {
123+
a[..] != b[..]
124+
}
125+
}
126+
127+
impl<T: PartialEq<U> + IsRawEqComparable<U>, U, const N: usize> SpecArrayEq<U, N> for T {
128+
#[cfg(bootstrap)]
129+
fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
130+
a[..] == b[..]
131+
}
132+
#[cfg(not(bootstrap))]
133+
fn spec_eq(a: &[T; N], b: &[U; N]) -> bool {
134+
// SAFETY: This is why `IsRawEqComparable` is an `unsafe trait`.
135+
unsafe {
136+
let b = &*b.as_ptr().cast::<[T; N]>();
137+
crate::intrinsics::raw_eq(a, b)
138+
}
139+
}
140+
fn spec_ne(a: &[T; N], b: &[U; N]) -> bool {
141+
!Self::spec_eq(a, b)
142+
}
143+
}
144+
145+
/// `U` exists on here mostly because `min_specialization` didn't let me
146+
/// repeat the `T` type parameter in the above specialization, so instead
147+
/// the `T == U` constraint comes from the impls on this.
148+
/// # Safety
149+
/// - Neither `Self` nor `U` has any padding.
150+
/// - `Self` and `U` have the same layout.
151+
/// - `Self: PartialEq<U>` is byte-wise (this means no floats, among other things)
152+
#[rustc_specialization_trait]
153+
unsafe trait IsRawEqComparable<U> {}
154+
155+
macro_rules! is_raw_comparable {
156+
($($t:ty),+) => {$(
157+
unsafe impl IsRawEqComparable<$t> for $t {}
158+
)+};
159+
}
160+
is_raw_comparable!(bool, char, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);

core/src/array/mod.rs

Lines changed: 1 addition & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::mem::{self, MaybeUninit};
1414
use crate::ops::{Index, IndexMut};
1515
use crate::slice::{Iter, IterMut};
1616

17+
mod equality;
1718
mod iter;
1819

1920
#[stable(feature = "array_value_iter", since = "1.51.0")]
@@ -230,118 +231,6 @@ where
230231
}
231232
}
232233

233-
#[stable(feature = "rust1", since = "1.0.0")]
234-
impl<A, B, const N: usize> PartialEq<[B; N]> for [A; N]
235-
where
236-
A: PartialEq<B>,
237-
{
238-
#[inline]
239-
fn eq(&self, other: &[B; N]) -> bool {
240-
self[..] == other[..]
241-
}
242-
#[inline]
243-
fn ne(&self, other: &[B; N]) -> bool {
244-
self[..] != other[..]
245-
}
246-
}
247-
248-
#[stable(feature = "rust1", since = "1.0.0")]
249-
impl<A, B, const N: usize> PartialEq<[B]> for [A; N]
250-
where
251-
A: PartialEq<B>,
252-
{
253-
#[inline]
254-
fn eq(&self, other: &[B]) -> bool {
255-
self[..] == other[..]
256-
}
257-
#[inline]
258-
fn ne(&self, other: &[B]) -> bool {
259-
self[..] != other[..]
260-
}
261-
}
262-
263-
#[stable(feature = "rust1", since = "1.0.0")]
264-
impl<A, B, const N: usize> PartialEq<[A; N]> for [B]
265-
where
266-
B: PartialEq<A>,
267-
{
268-
#[inline]
269-
fn eq(&self, other: &[A; N]) -> bool {
270-
self[..] == other[..]
271-
}
272-
#[inline]
273-
fn ne(&self, other: &[A; N]) -> bool {
274-
self[..] != other[..]
275-
}
276-
}
277-
278-
#[stable(feature = "rust1", since = "1.0.0")]
279-
impl<A, B, const N: usize> PartialEq<&[B]> for [A; N]
280-
where
281-
A: PartialEq<B>,
282-
{
283-
#[inline]
284-
fn eq(&self, other: &&[B]) -> bool {
285-
self[..] == other[..]
286-
}
287-
#[inline]
288-
fn ne(&self, other: &&[B]) -> bool {
289-
self[..] != other[..]
290-
}
291-
}
292-
293-
#[stable(feature = "rust1", since = "1.0.0")]
294-
impl<A, B, const N: usize> PartialEq<[A; N]> for &[B]
295-
where
296-
B: PartialEq<A>,
297-
{
298-
#[inline]
299-
fn eq(&self, other: &[A; N]) -> bool {
300-
self[..] == other[..]
301-
}
302-
#[inline]
303-
fn ne(&self, other: &[A; N]) -> bool {
304-
self[..] != other[..]
305-
}
306-
}
307-
308-
#[stable(feature = "rust1", since = "1.0.0")]
309-
impl<A, B, const N: usize> PartialEq<&mut [B]> for [A; N]
310-
where
311-
A: PartialEq<B>,
312-
{
313-
#[inline]
314-
fn eq(&self, other: &&mut [B]) -> bool {
315-
self[..] == other[..]
316-
}
317-
#[inline]
318-
fn ne(&self, other: &&mut [B]) -> bool {
319-
self[..] != other[..]
320-
}
321-
}
322-
323-
#[stable(feature = "rust1", since = "1.0.0")]
324-
impl<A, B, const N: usize> PartialEq<[A; N]> for &mut [B]
325-
where
326-
B: PartialEq<A>,
327-
{
328-
#[inline]
329-
fn eq(&self, other: &[A; N]) -> bool {
330-
self[..] == other[..]
331-
}
332-
#[inline]
333-
fn ne(&self, other: &[A; N]) -> bool {
334-
self[..] != other[..]
335-
}
336-
}
337-
338-
// NOTE: some less important impls are omitted to reduce code bloat
339-
// __impl_slice_eq2! { [A; $N], &'b [B; $N] }
340-
// __impl_slice_eq2! { [A; $N], &'b mut [B; $N] }
341-
342-
#[stable(feature = "rust1", since = "1.0.0")]
343-
impl<T: Eq, const N: usize> Eq for [T; N] {}
344-
345234
#[stable(feature = "rust1", since = "1.0.0")]
346235
impl<T: PartialOrd, const N: usize> PartialOrd for [T; N] {
347236
#[inline]

core/src/intrinsics.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,26 @@ extern "rust-intrinsic" {
19131913
/// Allocate at compile time. Should not be called at runtime.
19141914
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
19151915
pub fn const_allocate(size: usize, align: usize) -> *mut u8;
1916+
1917+
/// Determines whether the raw bytes of the two values are equal.
1918+
///
1919+
/// The is particularly handy for arrays, since it allows things like just
1920+
/// comparing `i96`s instead of forcing `alloca`s for `[6 x i16]`.
1921+
///
1922+
/// Above some backend-decided threshold this will emit calls to `memcmp`,
1923+
/// like slice equality does, instead of causing massive code size.
1924+
///
1925+
/// # Safety
1926+
///
1927+
/// It's UB to call this if any of the *bytes* in `*a` or `*b` are uninitialized.
1928+
/// Note that this is a stricter criterion than just the *values* being
1929+
/// fully-initialized: if `T` has padding, it's UB to call this intrinsic.
1930+
///
1931+
/// (The implementation is allowed to branch on the results of comparisons,
1932+
/// which is UB if any of their inputs are `undef`.)
1933+
#[cfg(not(bootstrap))]
1934+
#[rustc_const_unstable(feature = "const_intrinsic_raw_eq", issue = "none")]
1935+
pub fn raw_eq<T>(a: &T, b: &T) -> bool;
19161936
}
19171937

19181938
// Some functions are defined here because they accidentally got made

0 commit comments

Comments
 (0)