Skip to content

feat: lazy min>0 scan loops in deterministic chain with capture snapshot/restore - #138

Merged
jbachorik merged 4 commits into
mainfrom
feat/backtracking-lazy-capture
Sep 30, 2026
Merged

jbachorik merged 4 commits into
mainfrom
feat/backtracking-lazy-capture

Conversation

@jbachorik

@jbachorik jbachorik commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Admits lazy scan loops with mandatory iterations (x+?, x{2,}?, min <= MAX_CHAIN_LOOP_BOUND) into the DETERMINISTIC_CHAIN_BYTECODE route, previously routed to BITSTATE_CAPTURE:

  • PatternAnalyzer: admission window q.min >= 0 && q.min <= MAX_CHAIN_LOOP_BOUND for non-greedy unbounded loops; LAZY_LOOP first-set, emptyPrefix, and min-width (w += e.min) updated for min > 0 (mandatory-consume semantics: elements after a mandatory lazy loop can never start a match).
  • DeterministicChainBytecodeGenerator.emitLazyLoop: pre-roll loop consumes exactly min loop-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_ANCHOR failing after a structurally successful tail) leaked its capture writes into the winning skip path (e.g. ^(\S+?)(?::(\d+))?$ on "relative:12x" leaked g2="12"; JDK reports null).
  • Tests: behavioral parity vs java.util.regex for \d+?, <.+?>, a+?b, ^([^\s]+?)(?::([0-9]+))?$, capture-restore assertions, admission-boundary edges (MAX_CHAIN_LOOP_BOUND admitted, +1 declined, min=2 width), and first-set/emptyPrefix pins.
  • Docs: doc/2026-09-01-deterministic-chain-bytecode-design.md updated for the new LazyLoop grammar and the capture-snapshot restore rule.
  • One-lazy-loop invariant enforced: a chain branch containing a second lazy scan loop anywhere in its tree (nested tail, OPT/CAPTURE content, ALT_CHAIN alternative) now declines the chain family and stays on the linear BitState/PikeVM route — nested scans would run O(n²) per matches/match try and those entry points carry no work budget (a+?a+?b is the motivating shape). Backed by new DeterministicChainDetectorTest cases.
  • New JMH benchmark LazyScanLoopBenchmark covering 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 > 0 only 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+?b O(n²) routing) — both fixed in e507b87 and both review threads resolved.

Related Issue(s)

N/A

Change Type

  • Bug fix
  • New feature
  • Performance improvement
  • Refactoring (no functional change)
  • Documentation
  • Test improvement
  • Build/CI change

Checklist

  • I have read the CONTRIBUTING.md guidelines
  • All existing tests pass (./gradlew build)
  • I have added tests for my changes
  • I have updated documentation (if applicable)
  • My commits are signed

Performance Impact

The admission flip re-routes a class of lazy patterns from BITSTATE_CAPTURE to 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) with LazyScanLoopBenchmark, all shapes at commit e507b87. Throughput, ops/ms:

shape reggie (chain) JDK reggie/JDK
\d+? find 307,526 ± 135,401 16,766 ± 3,021 18.3x
^(.+?)\. find 2,770 ± 82 402 ± 36 6.9x
<.+?> find 82,859 ± 42,165 15,762 ± 2,508 5.3x

All three re-routed shapes clear the 1.5x-of-JDK acceptance bar (reggie ≥ ~0.67x JDK) with wide margin. The ^(.+?)\. input was hardened in e507b87 after 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 against 7962a07 (BitState route) was not run; the JDK baseline is the acceptance bar as specified.

Additional Notes

  • Structurally validated: existing StrategySelection*, DeterministicChainDetectorTest, DeterministicChainV3BytecodeTest, PikeVmCaptureRegressionTest all green; spotlessApply clean.
  • notes/ (transient cairn session wrap-ups) is now gitignored by this PR's chore commit.

@jbachorik jbachorik added enhancement New feature or request AI Generated or assisted by AI labels Sep 30, 2026
@jbachorik
jbachorik marked this pull request as ready for review September 30, 2026 10:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T10:25:44.957352Z dcdd66e Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@codecov-commenter

codecov-commenter commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.72131% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.9%. Comparing base (7962a07) to head (e507b87).

Files with missing lines Patch % Lines
...doghq/reggie/codegen/analysis/PatternAnalyzer.java 94.8% 1 Missing and 1 partial ⚠️
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     
Files with missing lines Coverage Δ
...n/codegen/DeterministicChainBytecodeGenerator.java 96.2% <100.0%> (+<0.1%) ⬆️
...doghq/reggie/codegen/analysis/PatternAnalyzer.java 78.7% <94.8%> (-0.2%) ⬇️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7962a07...e507b87. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: FAIL

The capture-heavy benchmark places a dot at index 1, so its lazy tail succeeds immediately and never measures the newly added capture restoration on failed attempts.

Open Bits AI session

🤖 Bits Code Review · Commit dcdd66e · @DataDog review to ask questions

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@jbachorik

Copy link
Copy Markdown
Collaborator Author

Fixed in e507b87 (addressing general review comment).

@jbachorik
jbachorik merged commit 028e4ef into main Sep 30, 2026
9 checks passed
@jbachorik
jbachorik deleted the feat/backtracking-lazy-capture branch September 30, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated or assisted by AI enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants