Skip to content

Commit 1e5065a

Browse files
committed
Auto merge of #150945 - scottmcm:tweak-slice-partial-eq, r=Mark-Simulacrum
Tweak `SlicePartialEq` to allow MIR-inlining the `compare_bytes` call #150265 disabled this because it was a net perf win, but let's see if we can tweak the structure of this to allow more inlining on this side while still not MIR-inlining the loop when it's not just `memcmp` and thus hopefully preserving the perf win. This should also allow MIR-inlining the length check, which was previously blocked, and thus might allow some obvious non-matches to optimize away as well.
2 parents a234ae6 + 51de309 commit 1e5065a

3 files changed

Lines changed: 455 additions & 181 deletions

File tree

library/core/src/slice/cmp.rs

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use super::{from_raw_parts, memchr};
44
use crate::ascii;
55
use crate::cmp::{self, BytewiseEq, Ordering};
66
use crate::intrinsics::compare_bytes;
7+
use crate::mem::SizedTypeProperties;
78
use crate::num::NonZero;
89
use crate::ops::ControlFlow;
910

@@ -15,7 +16,14 @@ where
1516
{
1617
#[inline]
1718
fn eq(&self, other: &[U]) -> bool {
18-
SlicePartialEq::equal(self, other)
19+
let len = self.len();
20+
if len == other.len() {
21+
// SAFETY: Just checked that they're the same length, and the pointers
22+
// come from references-to-slices so they're guaranteed readable.
23+
unsafe { SlicePartialEq::equal_same_length(self.as_ptr(), other.as_ptr(), len) }
24+
} else {
25+
false
26+
}
1927
}
2028
}
2129

@@ -95,12 +103,14 @@ impl<T: PartialOrd> PartialOrd for [T] {
95103
// intermediate trait for specialization of slice's PartialEq
96104
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
97105
const trait SlicePartialEq<B> {
98-
fn equal(&self, other: &[B]) -> bool;
106+
/// # Safety
107+
/// `lhs` and `rhs` are both readable for `len` elements
108+
unsafe fn equal_same_length(lhs: *const Self, rhs: *const B, len: usize) -> bool;
99109
}
100110

101111
// Generic slice equality
102112
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
103-
impl<A, B> const SlicePartialEq<B> for [A]
113+
impl<A, B> const SlicePartialEq<B> for A
104114
where
105115
A: [const] PartialEq<B>,
106116
{
@@ -109,19 +119,15 @@ where
109119
// such as in `<str as PartialEq>::eq`.
110120
// The codegen backend can still inline it later if needed.
111121
#[rustc_no_mir_inline]
112-
default fn equal(&self, other: &[B]) -> bool {
113-
if self.len() != other.len() {
114-
return false;
115-
}
116-
122+
default unsafe fn equal_same_length(lhs: *const Self, rhs: *const B, len: usize) -> bool {
117123
// Implemented as explicit indexing rather
118124
// than zipped iterators for performance reasons.
119125
// See PR https://github.com/rust-lang/rust/pull/116846
120-
// FIXME(const_hack): make this a `for idx in 0..self.len()` loop.
126+
// FIXME(const_hack): make this a `for idx in 0..len` loop.
121127
let mut idx = 0;
122-
while idx < self.len() {
123-
// bound checks are optimized away
124-
if self[idx] != other[idx] {
128+
while idx < len {
129+
// SAFETY: idx < len, so both are in-bounds and readable
130+
if unsafe { *lhs.add(idx) != *rhs.add(idx) } {
125131
return false;
126132
}
127133
idx += 1;
@@ -134,30 +140,18 @@ where
134140
// When each element can be compared byte-wise, we can compare all the bytes
135141
// from the whole size in one call to the intrinsics.
136142
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
137-
impl<A, B> const SlicePartialEq<B> for [A]
143+
impl<A, B> const SlicePartialEq<B> for A
138144
where
139145
A: [const] BytewiseEq<B>,
140146
{
141-
// This is usually a pretty good backend inlining candidate because the
142-
// intrinsic tends to just be `memcmp`. However, as of 2025-12 letting
143-
// MIR inline this makes reuse worse because it means that, for example,
144-
// `String::eq` doesn't inline, whereas by keeping this from inling all
145-
// the wrappers until the call to this disappear. If the heuristics have
146-
// changed and this is no longer fruitful, though, please do remove it.
147-
// In the mean time, it's fine to not inline it in MIR because the backend
148-
// will still inline it if it things it's important to do so.
149-
#[rustc_no_mir_inline]
150147
#[inline]
151-
fn equal(&self, other: &[B]) -> bool {
152-
if self.len() != other.len() {
153-
return false;
154-
}
155-
156-
// SAFETY: `self` and `other` are references and are thus guaranteed to be valid.
157-
// The two slices have been checked to have the same size above.
148+
unsafe fn equal_same_length(lhs: *const Self, rhs: *const B, len: usize) -> bool {
149+
// SAFETY: by our precondition, `lhs` and `rhs` are guaranteed to be valid
150+
// for reading `len` values, which also means the size is guaranteed
151+
// not to overflow because it exists in memory;
158152
unsafe {
159-
let size = size_of_val(self);
160-
compare_bytes(self.as_ptr() as *const u8, other.as_ptr() as *const u8, size) == 0
153+
let size = crate::intrinsics::unchecked_mul(len, Self::SIZE);
154+
compare_bytes(lhs as _, rhs as _, size) == 0
161155
}
162156
}
163157
}

0 commit comments

Comments
 (0)