Linux Kernel Selftest development
 help / color / mirror / Atom feed
From: Leon Hwang <leon.hwang@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Andrii Nakryiko <andrii@kernel.org>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Kumar Kartikeya Dwivedi <memxor@gmail.com>,
	Martin KaFai Lau <martin.lau@linux.dev>,
	Song Liu <song@kernel.org>,
	Yonghong Song <yonghong.song@linux.dev>,
	Jiri Olsa <jolsa@kernel.org>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	Ihor Solodrai <ihor.solodrai@linux.dev>,
	Shuah Khan <shuah@kernel.org>, Leon Hwang <leon.hwang@linux.dev>,
	Rong Tao <rongtao@cestc.cn>,
	Yuzuki Ishiyama <ishiyama@hpc.is.uec.ac.jp>,
	Viktor Malik <vmalik@redhat.com>,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Subject: [RFC PATCH bpf-next 4/6] bpf: Optimize string substring kfuncs
Date: Tue, 28 Jul 2026 22:57:25 +0800	[thread overview]
Message-ID: <20260728145727.45153-5-leon.hwang@linux.dev> (raw)
In-Reply-To: <20260728145727.45153-1-leon.hwang@linux.dev>

Use the word-at-a-time character finder to skip haystack regions which
cannot begin a match. For case-insensitive searches, check both ASCII
case variants of the first needle byte.

At each candidate, use a direct fault-safe word matcher. When both
strings have the same relative alignment, skip equal words and inspect
locally loaded bytes only for mismatches and case folding.

Keep needle-first access, fall back at the same byte after a word-load
fault, and retain the extra needle-byte check at the search limit so
fault and boundary behavior remain unchanged.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
 kernel/bpf/helpers.c | 142 +++++++++++++++++++++++++++++++++----------
 1 file changed, 111 insertions(+), 31 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index af29acc90245..93d5fa409cd7 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4281,11 +4281,90 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
 	return __bpf_strspn(s__ign, reject__ign, true);
 }
 
-static int __bpf_strnstr(const char *s1, const char *s2, size_t len,
-			 bool ignore_case)
+#define bpf_str_match_byte(byte1, byte2, ignore_case)	\
+do {							\
+	char __c1 = (byte1);				\
+	char __c2 = (byte2);				\
+							\
+	if (ignore_case) {				\
+		__c1 = tolower(__c1);			\
+		__c2 = tolower(__c2);			\
+	}						\
+	if (__c1 == '\0')				\
+		return -ENOENT;				\
+	if (__c1 != __c2)				\
+		return 0;				\
+} while (0)
+
+static __always_inline int
+bpf_str_match_at(const char *s1, const char *s2, size_t limit, bool ignore_case)
 {
-	char c1, c2;
-	int i, j;
+	const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
+	unsigned char byte1, byte2, *bytes1, *bytes2;
+	unsigned long word1, word2, data;
+	size_t pos = 0, word_end, i;
+
+	if (IS_ENABLED(CONFIG_KMSAN) ||
+	    !IS_ALIGNED((unsigned long)s1 ^ (unsigned long)s2, sizeof(word1)))
+		goto byte_at_a_time;
+
+	while (pos < limit && !IS_ALIGNED((unsigned long)(s1 + pos), sizeof(word1))) {
+		__get_kernel_nofault(&byte2, s2 + pos, unsigned char, err_out);
+		if (byte2 == '\0')
+			return 1;
+		__get_kernel_nofault(&byte1, s1 + pos, unsigned char, err_out);
+		bpf_str_match_byte(byte1, byte2, ignore_case);
+		pos++;
+	}
+
+	word_end = pos + round_down(limit - pos, sizeof(word1));
+	while (pos < word_end) {
+		__get_kernel_nofault(&word2, s2 + pos, unsigned long, byte_at_a_time);
+		if (has_zero(word2, &data, &constants))
+			goto byte_at_a_time;
+		__get_kernel_nofault(&word1, s1 + pos, unsigned long, byte_at_a_time);
+		if (word1 == word2) {
+			pos += sizeof(word1);
+			continue;
+		}
+
+		bytes1 = (unsigned char *)&word1;
+		bytes2 = (unsigned char *)&word2;
+		for (i = 0; i < sizeof(word1); i++)
+			bpf_str_match_byte(bytes1[i], bytes2[i], ignore_case);
+		pos += sizeof(word1);
+	}
+
+byte_at_a_time:
+	for (; pos < limit; pos++) {
+		__get_kernel_nofault(&byte2, s2 + pos, unsigned char, err_out);
+		if (byte2 == '\0')
+			return 1;
+		__get_kernel_nofault(&byte1, s1 + pos, unsigned char, err_out);
+		bpf_str_match_byte(byte1, byte2, ignore_case);
+	}
+
+	if (limit == XATTR_SIZE_MAX)
+		return -E2BIG;
+
+	/*
+	 * Read one extra byte from s2 to cover the case when it is a suffix
+	 * of the first limit bytes of s1.
+	 */
+	__get_kernel_nofault(&byte2, s2 + limit, unsigned char, err_out);
+	return byte2 == '\0' ? 1 : -ENOENT;
+
+err_out:
+	return -EFAULT;
+}
+
+#undef bpf_str_match_byte
+
+static int __bpf_strnstr(const char *s1, const char *s2, size_t len, bool ignore_case)
+{
+	unsigned char first, candidate;
+	size_t pos = 0, limit;
+	int ret, offset;
 
 	if (!copy_from_kernel_nofault_allowed(s1, 1) ||
 	    !copy_from_kernel_nofault_allowed(s2, 1)) {
@@ -4293,37 +4372,38 @@ static int __bpf_strnstr(const char *s1, const char *s2, size_t len,
 	}
 
 	guard(pagefault)();
-	for (i = 0; i < XATTR_SIZE_MAX; i++) {
-		for (j = 0; i + j <= len && j < XATTR_SIZE_MAX; j++) {
-			__get_kernel_nofault(&c2, s2 + j, char, err_out);
-			if (c2 == '\0')
-				return i;
-			/*
-			 * We allow reading an extra byte from s2 (note the
-			 * `i + j <= len` above) to cover the case when s2 is
-			 * a suffix of the first len chars of s1.
-			 */
-			if (i + j == len)
-				break;
-			__get_kernel_nofault(&c1, s1 + j, char, err_out);
-
-			if (ignore_case) {
-				c1 = tolower(c1);
-				c2 = tolower(c2);
-			}
+	__get_kernel_nofault(&first, s2, unsigned char, err_out);
+	if (first == '\0')
+		return 0;
 
-			if (c1 == '\0')
-				return -ENOENT;
-			if (c1 != c2)
-				break;
+	while (pos < XATTR_SIZE_MAX && pos < len) {
+		limit = min_t(size_t, len - pos, XATTR_SIZE_MAX - pos);
+		offset = bpf_str_find(s1 + pos, limit, first, ignore_case, true);
+		if (offset < 0) {
+			if (offset == -ENOENT && limit == XATTR_SIZE_MAX - pos)
+				return -E2BIG;
+			return offset;
 		}
-		if (j == XATTR_SIZE_MAX)
-			return -E2BIG;
-		if (i + j == len)
+		pos += offset;
+
+		/*
+		 * bpf_str_find() also returns the position of a terminating
+		 * NUL. Distinguish it from a first-character match.
+		 */
+		__get_kernel_nofault(&candidate, s1 + pos, unsigned char, err_out);
+		if (candidate == '\0')
 			return -ENOENT;
-		s1++;
+
+		limit = min_t(size_t, len - pos, XATTR_SIZE_MAX);
+		ret = bpf_str_match_at(s1 + pos, s2, limit, ignore_case);
+		if (ret > 0)
+			return pos;
+		if (ret < 0)
+			return ret;
+		pos++;
 	}
-	return -E2BIG;
+	return pos == XATTR_SIZE_MAX ? -E2BIG : -ENOENT;
+
 err_out:
 	return -EFAULT;
 }
-- 
2.55.0


  parent reply	other threads:[~2026-07-28 14:58 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-28 14:57 [RFC PATCH bpf-next 0/6] bpf: Optimize string kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 1/6] bpf: Optimize string scan kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 2/6] bpf: Optimize string comparison kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 3/6] bpf: Optimize string span kfuncs Leon Hwang
2026-07-28 14:57 ` Leon Hwang [this message]
2026-07-28 14:57 ` [RFC PATCH bpf-next 5/6] selftests/bpf: Exercise word-at-a-time string kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 6/6] selftests/bpf: Benchmark " Leon Hwang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260728145727.45153-5-leon.hwang@linux.dev \
    --to=leon.hwang@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=ishiyama@hpc.is.uec.ac.jp \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=rongtao@cestc.cn \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=vmalik@redhat.com \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox