Commit 8d6e030
perf(builtin): guaranteed-linear KMP fallback for substring search
The SIMD two-anchor find/rev_find already cut over to a fallback after
enough dense false-anchor candidates, but that fallback was a naive
scalar scan — still O(target * pattern) on adversarial inputs where
both anchors and long pattern prefixes recur throughout the target
(e.g. needle 'a'*32 + 'Z' + 'a'*31 over all-'a' text). This replaces
the naive fallback with KMP, making find/rev_find O(target + pattern)
in the worst case: the existing cutover budget trips on the 65th
failed verification (`failures > 64`), capping pre-fallback
verification at O(pattern) work, and the fallback itself
is linear. The failure-table allocation is paid only after cutover,
i.e. exclusively on pathological inputs. Resolves the standing TODO on
find/rev_find ("consider using Two-Way algorithm to ensure linear time
complexity") — KMP behind the existing budget gives the same guarantee
with less machinery.
The reverse fallback runs forward KMP over the prefix that can still
contain a hit and keeps the rightmost match, continuing through
overlaps via the failure table.
The idea comes from PR #3786 (mizchi), whose dense-case analysis and
verified-fallback design predate the SIMD two-anchor work that
superseded its scanner; this lands its remaining piece.
Benchmarks (moon bench --release, main -> this branch):
adversary find m=64 n=4096: native 48.6 -> 6.1us (8.0x),
js 453 -> 27.3us (16.6x), wasm-gc 112 -> 10.6us (10.6x)
adversary rev_find m=64 n=4096: native 48.8 -> 7.0us,
js 456 -> 27.4us, wasm-gc 112 -> 12.4us
scaling adversary m=512 n=65536: native 7.20ms -> 91us (79x),
js 56.2ms -> 433us (130x), wasm-gc 15.0ms -> 175us (86x) —
the O(n*m) -> O(n+m) signature
fast paths (dense miss, rare hit): par on all backends; the SIMD
two-anchor path is untouched.
Tests: whitebox KMP tests over periodic/overlapping patterns and
boundary starts; blackbox end-to-end tests built from decoy blocks that
each contribute exactly one false-anchor candidate, so both directions
provably exhaust the 65-failure budget before the interesting region —
dense-anchor misses, hits beyond the cutover point (including a first
occurrence that straddles the decoy/hits boundary), reverse cutover
resolving overlapping hits to the rightmost start, and forward/reverse
naive-reference cross-checks over fast-path and fallback
configurations. string_find_adversary_bench_test.mbt commits the
adversarial workloads so the numbers above are reproducible.
Mutation-verified: zeroing the failure table fails 2 tests; dropping
the overlap continuation in the reverse fallback fails 2.
Codex CLI review (ultra, round 1): production implementation verified
sound by exhaustive binary-alphabet modeling (267M helper-contract
cases, 89M full-route cases, no counterexample vs naive search),
including the handoff off-by-ones and the skipped-position reasoning;
three P2 findings all in tests/prose — a whitebox test argument that
violated the reverse helper's bound (out-of-bounds unsafe_get, fixed
to 7 and the precondition documented), an overlap test that never
reached the fallback (rebuilt with decoy blocks), and overstated
naive-reference coverage (rebuilt with both directions and both
paths).
Copilot review addressed: the cutover comments now describe BOTH
triggers — the early ratio check (failures > 4 + scanned/8, tripping
as soon as the 5th failure for candidates packed near the scan start)
and the hard cap (failures > 64) — and the fallback-test header notes
its spaced decoys deterministically force the hard-cap path.
QuickCheck added (string_find_quickcheck_test.mbt): a general
random-text/needle equivalence property against the naive references
(dense two-symbol texts randomly exercise the early ratio cutover),
and a fallback-forcing property that surrounds a randomized hit/decoy
mixture with 70 corrupted blocks per side (anchors intact, one
interior char flipped), fuzzing the KMP fallback itself with
randomized needle shapes. Zeroing the failure table is falsified by
the property after 2 cases.
Round 2 (ultra) verified the fixes: "All three P2 fixes are correct
and complete. Whitebox bound is safely 7, with the precondition
documented. Decoy arithmetic, forward result 5699, reverse result 41,
and 65th-failure cutovers check out. Naive references use correct
inclusive bounds and cover fast/fallback paths in both directions."
Round 3 (ultra) verified the Copilot-prompted prose and the QuickCheck
layer: "Ratio cutover can first occur on failure 5 when scanned < 8;
cutover is always by failure 65. Period-82 decoys never trigger the
ratio branch and deterministically hit the hard cap. All 2,835 valid
corrupted-block constructions preserve anchors, differ from the needle,
and cannot form accidental cross-block matches. Both directions force
fallback before the mixture."
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Codex CLI <codex@openai.com>
Signed-off-by: Codex CLI <codex@openai.com>1 parent dc17f50 commit 8d6e030
5 files changed
Lines changed: 420 additions & 53 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
211 | 211 | | |
212 | 212 | | |
213 | 213 | | |
214 | | - | |
| 214 | + | |
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
| |||
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
262 | | - | |
| 262 | + | |
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
| |||
269 | 269 | | |
270 | 270 | | |
271 | 271 | | |
272 | | - | |
273 | | - | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
274 | 280 | | |
275 | 281 | | |
276 | 282 | | |
277 | 283 | | |
278 | 284 | | |
279 | 285 | | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
285 | 314 | | |
286 | 315 | | |
287 | 316 | | |
288 | 317 | | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
296 | | - | |
297 | | - | |
298 | | - | |
299 | | - | |
300 | | - | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
301 | 326 | | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
306 | 332 | | |
307 | | - | |
308 | | - | |
309 | 333 | | |
| 334 | + | |
310 | 335 | | |
311 | 336 | | |
312 | 337 | | |
313 | | - | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
318 | 347 | | |
319 | 348 | | |
320 | 349 | | |
321 | 350 | | |
322 | 351 | | |
323 | | - | |
324 | | - | |
325 | | - | |
326 | | - | |
327 | | - | |
328 | | - | |
329 | | - | |
330 | | - | |
331 | | - | |
332 | | - | |
333 | | - | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
334 | 363 | | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
| 364 | + | |
| 365 | + | |
339 | 366 | | |
340 | | - | |
341 | | - | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
342 | 376 | | |
343 | 377 | | |
344 | 378 | | |
345 | 379 | | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
346 | 399 | | |
347 | 400 | | |
348 | 401 | | |
| |||
0 commit comments