Repository navigation
feat: lazy min>0 scan loops in deterministic chain with capture snapshot/restore - #138
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #138 +/- ##
=========================================
- Coverage 84.9% 84.9% -0.1%
Complexity 1 1
=========================================
Files 163 163
Lines 48850 48904 +54
Branches 6972 6982 +10
=========================================
+ Hits 41478 41521 +43
- Misses 5367 5377 +10
- Partials 2005 2006 +1
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcdd66ee8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Fixed in e507b87 (addressing general review comment). |
What does this PR do?
Admits lazy scan loops with mandatory iterations (
x+?,x{2,}?,min <= MAX_CHAIN_LOOP_BOUND) into theDETERMINISTIC_CHAIN_BYTECODEroute, previously routed toBITSTATE_CAPTURE:PatternAnalyzer: admission windowq.min >= 0 && q.min <= MAX_CHAIN_LOOP_BOUNDfor non-greedy unbounded loops;LAZY_LOOPfirst-set,emptyPrefix, and min-width (w += e.min) updated formin > 0(mandatory-consume semantics: elements after a mandatory lazy loop can never start a match).DeterministicChainBytecodeGenerator.emitLazyLoop: pre-roll loop consumes exactlyminloop-class chars before the first tail try (JDK lazy order,k = min, min+1, …), and every failed tail try restores a capture snapshot taken at loop entry — fixing a wrong-group-capture leak where a lazy-predicate-rejected try (POS_EQ_LEN/END_ANCHORfailing after a structurally successful tail) leaked its capture writes into the winning skip path (e.g.^(\S+?)(?::(\d+))?$on"relative:12x"leakedg2="12"; JDK reportsnull).java.util.regexfor\d+?,<.+?>,a+?b,^([^\s]+?)(?::([0-9]+))?$, capture-restore assertions, admission-boundary edges (MAX_CHAIN_LOOP_BOUNDadmitted,+1declined,min=2width), and first-set/emptyPrefixpins.doc/2026-09-01-deterministic-chain-bytecode-design.mdupdated for the newLazyLoopgrammar and the capture-snapshot restore rule.matches/matchtry and those entry points carry no work budget (a+?a+?bis the motivating shape). Backed by newDeterministicChainDetectorTestcases.LazyScanLoopBenchmarkcovering the re-routed shapes; its^(.+?)\.input is shaped to force ~207 failed tail tries so the per-failed-try capture-restore path is actually exercised.Motivation
Lazy quantifiers with mandatory iterations were excluded from the deterministic-chain strategy, forcing the slower BitState capture route for common shapes (
\d+?,<.+?>,^(.+?)\.). The change extends the existing scan-gate machinery:min > 0only shifts the first tail try; the linear scan itself is unchanged. Capture hygiene (snapshot/restore per failed try) was required for correctness once predicate-rejected tails could write captures.The change was reviewed by the sphinx multi-stream review pipeline (3 rounds + verification: 12 findings, 0 critical/high); all 12 fix selections (behavioral tests, boundary-edge tests, benchmark evidence, first-set pins) were implemented and are included in this PR.
After pushing, the PR's automated code review (Bits/Codex) raised two further P1/P2 findings — the short-circuiting benchmark fixture and the unenforced one-lazy-loop invariant (
a+?a+?bO(n²) routing) — both fixed ine507b87and both review threads resolved.Related Issue(s)
N/A
Change Type
Checklist
./gradlew build)Performance Impact
The admission flip re-routes a class of lazy patterns from
BITSTATE_CAPTUREto the linear deterministic-chain scan. Cost added to the chain route: a capture snapshot at loop entry plus an O(g) register-local restore per failed tail try (g = pattern group count, small, no allocation) — dwarfed by the tail re-walk every failed try already performs.Measured on
workspace-jb(16 cores, JDK 26.0.2.1) withLazyScanLoopBenchmark, all shapes at commite507b87. Throughput, ops/ms:\d+?find^(.+?)\.find<.+?>findAll three re-routed shapes clear the 1.5x-of-JDK acceptance bar (reggie ≥ ~0.67x JDK) with wide margin. The
^(.+?)\.input was hardened ine507b87after review feedback: the original fixture's first dot at index 1 let the lazy tail succeed on the first try, so the per-failed-try capture-restore path never executed; the fixed input (207-char token ending in a dot) forces ~207 failed tail tries, and the row above is from a focused re-run of that shape (3×2s warmup, 5×2s iterations) on the hardened input — reggie stays ~6.9x ahead while performing the capture restore on every failed try. The\d+?/<.+?>rows use the standard short run (1 warmup, 3×1s) — wide error bars, but margins are large. A same-branch before/after against7962a07(BitState route) was not run; the JDK baseline is the acceptance bar as specified.Additional Notes
StrategySelection*,DeterministicChainDetectorTest,DeterministicChainV3BytecodeTest,PikeVmCaptureRegressionTestall green;spotlessApplyclean.notes/(transient cairn session wrap-ups) is now gitignored by this PR's chore commit.