Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9de3d01

Browse files
committedMay 5, 2023
Tune the is_ascii implementation used for short slices
1 parent 1cfcf71 commit 9de3d01

File tree

3 files changed

+55
-10
lines changed

3 files changed

+55
-10
lines changed
 

‎library/core/src/slice/ascii.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,24 @@ const fn contains_nonascii(v: usize) -> bool {
268268
(NONASCII_MASK & v) != 0
269269
}
270270

271+
/// ASCII test *without* the chunk-at-a-time optimizations.
272+
///
273+
/// This is carefully structured to produce nice small code -- it's smaller in
274+
/// `-O` than what the "obvious" ways produces under `-C opt-level=s`. If you
275+
/// touch it, be sure to run (and update if needed) the assembly test.
276+
#[unstable(feature = "str_internals", issue = "none")]
277+
#[doc(hidden)]
278+
#[inline]
279+
pub const fn is_ascii_simple(mut bytes: &[u8]) -> bool {
280+
while let [rest @ .., last] = bytes {
281+
if !last.is_ascii() {
282+
break;
283+
}
284+
bytes = rest;
285+
}
286+
bytes.is_empty()
287+
}
288+
271289
/// Optimized ASCII test that will use usize-at-a-time operations instead of
272290
/// byte-at-a-time operations (when possible).
273291
///
@@ -293,16 +311,7 @@ const fn is_ascii(s: &[u8]) -> bool {
293311
// We also do this for architectures where `size_of::<usize>()` isn't
294312
// sufficient alignment for `usize`, because it's a weird edge case.
295313
if len < USIZE_SIZE || len < align_offset || USIZE_SIZE < mem::align_of::<usize>() {
296-
// FIXME: once iterators and closures can be used in `const fn`,
297-
// return s.iter().all(|b| b.is_ascii());
298-
let mut i = 0;
299-
while i < len {
300-
if !s[i].is_ascii() {
301-
return false;
302-
}
303-
i += 1;
304-
}
305-
return true;
314+
return is_ascii_simple(s);
306315
}
307316

308317
// We always read the first word unaligned, which means `align_offset` is

‎library/core/src/slice/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ mod raw;
4444
mod rotate;
4545
mod specialize;
4646

47+
#[unstable(feature = "str_internals", issue = "none")]
48+
#[doc(hidden)]
49+
pub use ascii::is_ascii_simple;
50+
4751
#[stable(feature = "rust1", since = "1.0.0")]
4852
pub use iter::{Chunks, ChunksMut, Windows};
4953
#[stable(feature = "rust1", since = "1.0.0")]

‎tests/assembly/slice-is_ascii.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// assembly-output: emit-asm
2+
// compile-flags: --crate-type=lib -O -C llvm-args=-x86-asm-syntax=intel
3+
// no-system-llvm
4+
// only-x86_64
5+
// ignore-sgx
6+
// ignore-debug
7+
8+
#![feature(str_internals)]
9+
10+
// CHECK-LABEL: is_ascii_simple_demo:
11+
#[no_mangle]
12+
pub fn is_ascii_simple_demo(bytes: &[u8]) -> bool {
13+
// Linux (System V): pointer is rdi; length is rsi
14+
// Windows: pointer is rcx; length is rdx.
15+
16+
// CHECK-NOT: mov
17+
// CHECK-NOT: test
18+
// CHECK-NOT: cmp
19+
20+
// CHECK: .[[LOOPHEAD:.+]]:
21+
// CHECK-NEXT: mov [[TEMP:.+]], [[LEN:rsi|rdx]]
22+
// CHECK-NEXT: sub [[LEN]], 1
23+
// CHECK-NEXT: jb .[[LOOPEXIT:.+]]
24+
// CHECK-NEXT: cmp byte ptr [{{rdi|rcx}} + [[TEMP]] - 1], 0
25+
// CHECK-NEXT: jns .[[LOOPHEAD]]
26+
27+
// CHECK-NEXT: .[[LOOPEXIT]]:
28+
// CHECK-NEXT: test [[TEMP]], [[TEMP]]
29+
// CHECK-NEXT: sete al
30+
// CHECK-NEXT: ret
31+
core::slice::is_ascii_simple(bytes)
32+
}

0 commit comments

Comments
 (0)
Please sign in to comment.