From 17b70e6d6a02ca09bdb6f9214e73305731a03410 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Thu, 22 Oct 2026 08:57:59 -0500 Subject: [PATCH] Find Buffer search candidates with memchr --- .../7249-buffer-index-of-candidates.ts.json | 6 +++++ packages/runtime/src/scr_bytes.c | 14 +++++++--- .../corpus/7249-buffer-index-of-candidates.ts | 27 +++++++++++++++++++ .../7249-buffer-index-of-candidates.ts.json | 1 + 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7249-buffer-index-of-candidates.ts.json create mode 100644 tests/corpus/7249-buffer-index-of-candidates.ts create mode 100644 tests/harness/corpus-costs/7249-buffer-index-of-candidates.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7249-buffer-index-of-candidates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7249-buffer-index-of-candidates.ts.json new file mode 100644 index 000000000..f5299cb9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7249-buffer-index-of-candidates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7249-buffer-index-of-candidates.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 7a6a76cf9..183174555 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -3271,16 +3271,24 @@ double scr_bytes_index_of(const ScrBytes *b, const ScrBytes *needle, double off, } if (nlen == 0) return o > (double)len ? (double)len : o; /* '' matches at the clamp */ if (nlen > len) return -1; + /* Candidates are the positions holding the needle's first byte; only + * those compare the rest. */ + const uint8_t first = needle->data[0]; if (fwd) { size_t start = o > (double)len ? len : (size_t)o; - for (size_t i = start; i + nlen <= len; i++) { - if (memcmp(b->data + i, needle->data, nlen) == 0) return (double)i; + const uint8_t *at = b->data + start, *end = b->data + (len - nlen) + 1; + while (at < end) { + at = memchr(at, first, (size_t)(end - at)); + if (!at) return -1; + if (nlen == 1 || memcmp(at + 1, needle->data + 1, nlen - 1) == 0) return (double)(at - b->data); + at++; } return -1; } size_t start = o > (double)(len - nlen) ? len - nlen : (size_t)o; for (size_t i = start;; i--) { - if (memcmp(b->data + i, needle->data, nlen) == 0) return (double)i; + if (b->data[i] == first && (nlen == 1 || memcmp(b->data + i + 1, needle->data + 1, nlen - 1) == 0)) + return (double)i; if (i == 0) break; } return -1; diff --git a/tests/corpus/7249-buffer-index-of-candidates.ts b/tests/corpus/7249-buffer-index-of-candidates.ts new file mode 100644 index 000000000..ae6c2ae23 --- /dev/null +++ b/tests/corpus/7249-buffer-index-of-candidates.ts @@ -0,0 +1,27 @@ +// Buffer indexOf, lastIndexOf and includes with needles whose first byte +// recurs: a candidate whose later bytes differ is skipped, matches at the +// first and last positions are found, byteOffset clamps and counts from the +// end, and the search reads only the buffer's own window of its memory. +const text = Buffer.from("aab aaab aaaab ab\nab"); +console.log(text.indexOf("ab"), text.indexOf("aab"), text.indexOf("aaab"), text.indexOf("aaaab"), text.indexOf("aaaaab")); +console.log(text.lastIndexOf("ab"), text.lastIndexOf("aab"), text.lastIndexOf("aaab"), text.lastIndexOf("b\na")); +console.log(text.indexOf("ab", 2), text.indexOf("ab", -3), text.indexOf("ab", 100), text.indexOf("ab", -100)); +console.log(text.lastIndexOf("ab", 5), text.lastIndexOf("ab", -4), text.lastIndexOf("ab", 0), text.lastIndexOf("aa", 1)); +console.log(text.indexOf(10), text.indexOf(97, 3), text.lastIndexOf(98), text.lastIndexOf(97, -2), text.includes("b a")); +console.log(text.indexOf(""), text.indexOf("", 4), text.indexOf("", 100), text.lastIndexOf(""), text.indexOf("x")); +console.log(text.indexOf(Buffer.from("a\nab")), text.indexOf("ab\nab"), text.indexOf("ab\nabc"), text.lastIndexOf("aab", 4)); + +// A window into a larger buffer never sees the bytes outside it. +const whole = Buffer.from("xxneedlexx needle needlexx"); +const window = whole.subarray(3, 22); +console.log(window.toString(), window.indexOf("needle"), window.lastIndexOf("needle"), window.indexOf("xx"), window.includes("xne")); +console.log(window.indexOf("eedle", 1), window.lastIndexOf("eed"), window.indexOf("needlex"), window.lastIndexOf("n", 0)); + +// A long search with many first-byte candidates. +const long = Buffer.alloc(20000, "ab"); +long.write("abc", 19990); +console.log(long.indexOf("abc"), long.lastIndexOf("aba"), long.indexOf("abc", 19991), long.lastIndexOf("abc"), long.indexOf("bb")); +const lines: number[] = []; +const fasta = Buffer.from(">ONE\nACGT\nTTGA\n>TWO\nGGCC\n"); +for (let at = fasta.indexOf(10); at !== -1; at = fasta.indexOf(10, at + 1)) lines.push(at); +console.log(lines.join(","), fasta.indexOf(62, 1), fasta.lastIndexOf(62)); diff --git a/tests/harness/corpus-costs/7249-buffer-index-of-candidates.ts.json b/tests/harness/corpus-costs/7249-buffer-index-of-candidates.ts.json new file mode 100644 index 000000000..5c265ea01 --- /dev/null +++ b/tests/harness/corpus-costs/7249-buffer-index-of-candidates.ts.json @@ -0,0 +1 @@ +{ "plain": 2870, "san": 3656 }