From 0439f4ef09af50b440af245a0f2173442fbcd567 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:32:21 +0100 Subject: [PATCH 1/7] Hint that memchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 7 ++++++- library/coretests/tests/slice.rs | 11 +++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 1e1053583a617..6762015181d85 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -28,7 +28,12 @@ pub const fn memchr(x: u8, text: &[u8]) -> Option { return memchr_naive(x, text); } - memchr_aligned(x, text) + let result = memchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result } #[inline] diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index a4db7304fff90..b05f54d4df0a2 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1781,6 +1781,17 @@ pub mod memchr { assert_eq!(None, memchr(b'a', b"xyz")); } + #[test] + fn each_alignment() { + let mut data = [1u8; 64]; + let needle = 2; + let pos = 40; + data[pos] = needle; + for start in 0..16 { + assert_eq!(Some(pos - start), memchr(needle, &data[start..])); + } + } + #[test] fn matches_one_reversed() { assert_eq!(Some(0), memrchr(b'a', b"a")); diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs new file mode 100644 index 0000000000000..fbdbdcc3fe9f3 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -0,0 +1,13 @@ +// Ensure `memchr` communicates that a returned index is in bounds. +//@ compile-flags: -Copt-level=3 -Zinline-mir=false +//@ only-64bit + +#![crate_type = "lib"] + +// CHECK-LABEL: @find_char +#[no_mangle] +pub fn find_char(haystack: &str, needle: char) -> Option { + // CHECK-NOT: phi { i64, i64 } + // CHECK: ret { i64, i64 } + haystack.find(needle) +} From c1f36d5f4bde0f955e9d0cbdec406d22b5044360 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:29:23 +0100 Subject: [PATCH 2/7] Hint that memrchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 10 ++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 6762015181d85..c83e8b218da08 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -112,8 +112,18 @@ const fn memchr_aligned(x: u8, text: &[u8]) -> Option { } /// Returns the last index matching the byte `x` in `text`. +#[inline] #[must_use] pub fn memrchr(x: u8, text: &[u8]) -> Option { + let result = memrchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memrchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result +} + +fn memrchr_aligned(x: u8, text: &[u8]) -> Option { // Scan for a single byte value by reading two `usize` words at a time. // // Split `text` in three parts: diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index fbdbdcc3fe9f3..f18335075451c 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -3,6 +3,11 @@ //@ only-64bit #![crate_type = "lib"] +#![feature(slice_internals)] + +extern crate core; + +use core::slice::memchr::memrchr; // CHECK-LABEL: @find_char #[no_mangle] @@ -11,3 +16,11 @@ pub fn find_char(haystack: &str, needle: char) -> Option { // CHECK: ret { i64, i64 } haystack.find(needle) } + +// CHECK-LABEL: @rfind_byte +#[no_mangle] +pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { + // CHECK-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memrchr(needle, haystack).map(|index| haystack[index]) +} From 844c01e43be5782643646d73a6f65539db046a33 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:14:14 +0000 Subject: [PATCH 3/7] Cover memchr fast path with bounds assertion --- library/core/src/slice/memchr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index c83e8b218da08..017661f0448c2 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,13 +24,13 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - return memchr_naive(x, text); - } - - let result = memchr_aligned(x, text); + let result = if text.len() < 2 * USIZE_BYTES { + memchr_naive(x, text) + } else { + memchr_aligned(x, text) + }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 49c1f02279a37b85fcd9448dc7b87e20923f57dd Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:32:44 +0000 Subject: [PATCH 4/7] Fix memchr result CI checks --- library/core/src/slice/memchr.rs | 7 ++----- tests/codegen-llvm/lib-optimizations/memchr-result.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 017661f0448c2..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,11 +24,8 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = if text.len() < 2 * USIZE_BYTES { - memchr_naive(x, text) - } else { - memchr_aligned(x, text) - }; + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index f18335075451c..77abc33adde83 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,6 +1,6 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-64bit +//@ only-x86_64 #![crate_type = "lib"] #![feature(slice_internals)] From 807750a1fcdb31f4bf527089ff44cf95ac199046 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:57:45 +0000 Subject: [PATCH 5/7] Preserve memchr codegen on LLVM 21 --- library/core/src/slice/memchr.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index fb99e86139d7e..68826ecac31f3 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,10 +24,18 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = - if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; + if text.len() < 2 * USIZE_BYTES { + let result = memchr_naive(x, text); + if let Some(index) = result { + // SAFETY: `memchr_naive` only returns an index from within `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + return result; + } + + let result = memchr_aligned(x, text); if let Some(index) = result { - // SAFETY: Both implementations only return an index from within `text`. + // SAFETY: `memchr_aligned` only returns an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From cd8a97b4b21a9c2f8fa1ec9eed2cc6c83c489ddb Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:38:32 +0000 Subject: [PATCH 6/7] Handle LLVM 21 in memchr result codegen test LLVM 21 preserves the bounds assumption but does not eliminate the aggregate phi that LLVM 22 removes. Check each version's supported optimization and restore the shared postcondition so direct callers can eliminate bounds checks. --- library/core/src/slice/memchr.rs | 14 +++----------- .../lib-optimizations/memchr-result.rs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 68826ecac31f3..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,18 +24,10 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - let result = memchr_naive(x, text); - if let Some(index) = result { - // SAFETY: `memchr_naive` only returns an index from within `text`. - unsafe { crate::hint::assert_unchecked(index < text.len()) }; - } - return result; - } - - let result = memchr_aligned(x, text); + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns an index from within `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index 77abc33adde83..beeab470c08af 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,22 +1,34 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false //@ only-x86_64 +//@ revisions: llvm-old llvm-new +//@ [llvm-old] max-llvm-major-version: 21 +//@ [llvm-new] min-llvm-version: 22 #![crate_type = "lib"] #![feature(slice_internals)] extern crate core; -use core::slice::memchr::memrchr; +use core::slice::memchr::{memchr, memrchr}; // CHECK-LABEL: @find_char #[no_mangle] pub fn find_char(haystack: &str, needle: char) -> Option { - // CHECK-NOT: phi { i64, i64 } + // llvm-old: call void @llvm.assume + // llvm-new-NOT: phi { i64, i64 } // CHECK: ret { i64, i64 } haystack.find(needle) } +// CHECK-LABEL: @find_byte +#[no_mangle] +pub fn find_byte(haystack: &[u8], needle: u8) -> Option { + // llvm-new-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memchr(needle, haystack).map(|index| haystack[index]) +} + // CHECK-LABEL: @rfind_byte #[no_mangle] pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { From 08e7eaf9629fb8d325fea229b165e6cc6d173b4e Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:05:10 +0000 Subject: [PATCH 7/7] Remove fragile memchr codegen test --- .../lib-optimizations/memchr-result.rs | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs deleted file mode 100644 index beeab470c08af..0000000000000 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Ensure `memchr` communicates that a returned index is in bounds. -//@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-x86_64 -//@ revisions: llvm-old llvm-new -//@ [llvm-old] max-llvm-major-version: 21 -//@ [llvm-new] min-llvm-version: 22 - -#![crate_type = "lib"] -#![feature(slice_internals)] - -extern crate core; - -use core::slice::memchr::{memchr, memrchr}; - -// CHECK-LABEL: @find_char -#[no_mangle] -pub fn find_char(haystack: &str, needle: char) -> Option { - // llvm-old: call void @llvm.assume - // llvm-new-NOT: phi { i64, i64 } - // CHECK: ret { i64, i64 } - haystack.find(needle) -} - -// CHECK-LABEL: @find_byte -#[no_mangle] -pub fn find_byte(haystack: &[u8], needle: u8) -> Option { - // llvm-new-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memchr(needle, haystack).map(|index| haystack[index]) -} - -// CHECK-LABEL: @rfind_byte -#[no_mangle] -pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { - // CHECK-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memrchr(needle, haystack).map(|index| haystack[index]) -}