Skip to content

Commit c8c5a58

Browse files
committed
Tune the is_ascii implementation used for short slices
1 parent 1cfcf71 commit c8c5a58

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

library/core/src/slice/ascii.rs

+19-10
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

+4
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

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

0 commit comments

Comments
 (0)