Skip to content

0.4.0: backend regex replacement readiness — literal-prefilter arc, hybrid engines, JIT sizing, parity batteries + review hardening (42 findings addressed) - #129

Merged
jbachorik merged 10 commits into
mainfrom
feat/dd_backend_check
Sep 24, 2026
Merged

jbachorik merged 10 commits into
mainfrom
feat/dd_backend_check

Conversation

@jbachorik

@jbachorik jbachorik commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

Makes the regex engine (reggie) ready to replace java.util.regex in high-volume backend workloads. 10 commits across four areas — all measured on an idle 16-core Linux box with same-run jdk/rust controls:

  1. JDK semantic parity sweep (fixes + feature parity)
  2. Compile infrastructure (deadline, huge-charset codegen, NUL audit)
  3. Counted-loop perf + 4th benchmark lane (rust)
  4. The prefilter / hybrid-engine / parity arc proper
  5. Review hardening: 42 findings from an adversarial multi-round review addressed (28 implemented, 14 verified already handled)

Commits are squashed to one commit per logical area:

Performance — real-corpus scan (528 real production patterns × 6 log lines, RealCorpusScanBenchmark):

  • R1 required-literal rejection prefilter (sound AST extraction; 63% of corpus instant-rejects): no-match sweep 33.9ms → 3.7ms (9.1x)
  • R2 linear find() for .*-prefix recursive-descent patterns + R1b 1-char and ASCII-ci required facts: no-match 3.7ms → 983µs (15.8x vs 0.3.0 baseline; 1.79x ahead of rust-regex)
  • Matched-side tranches — JIT-sized DFA_SWITCH codegen (generated methods over HugeMethodLimit ran interpreted), hybrid engine admission (start-anchored, unanchored, lazy-aware, RD give-back), RE2 leftmost-first thread pruning, alternation-priority retry: matched 340µs → 180µs (1.2x ahead of rust, 2x ahead of JDK)
  • 1-char prefilter facts now use the char indexOf intrinsic (the String overload's first-char scan is 5.7x slower on x86): corpus no-match ~9% relative; IAST LdapNoMatch LONG 276 → 2,026 ops/ms (7.3x)
  • Net per-pair on the corpus: reggie 0.49µs matched / 0.25µs no-match vs jdk 1.81/11.97, rust 0.62/0.66, re2j 6.58/5.14 — reggie leads every shape in the 4-engine matrix

Correctness:

  • Three real-input parity fixes found by the new real-input battery (527 real test lines × corpus patterns, 270k pairs): nullable-tail group spans, backref suffix/end-anchor spans, parse-refusal fallback bypass
  • find-span parity fixes: two \b find() divergences, greedy \Z span, LazyDFA leftmost-start, generated-NFA findFrom literal jump, hybrid span re-match anchor-context
  • Alternation-priority refusals re-routed to PikeVM (10 → 7 honest refusals; 521/528 native coverage)
  • Zero divergences now: boolean parity, span parity, find parity, real-input parity — all four batteries are permanent tests
  • JIT method-size gate: generated classes whose largest method exceeds 8000 bytecodes decline to the JDK fallback under ALLOW_JDK_FALLBACK (2 monster patterns)

Benchmarks: re2j corpus lane + rust (JNI) IAST lanes added; benchmark corpus synced with the parity-test corpus (same TSV).

Docs: changelog for the full 0.4.0 arc.

Review hardening (adversarial multi-round review)

The whole branch was re-reviewed by a 5-round adversarial review pipeline (5 specialist roles + hypothesis-driven gap analysis, mutation/adversary verification; 305 regions, 83 files), producing 42 valid findings (0 critical, 0 high after verification). All 42 are addressed on this branch — 28 implemented in this pass, 14 verified already handled by earlier commits:

Runtime / resource lifecycle:

  • Compile caches (LITERAL_CACHE, COUNTED_LOOP_NFA_CACHE, HYBRID_CACHE) are size-capped via a shared insertion helper (clear-on-overflow, -Dreggie.compile.cacheMaxEntries, default 10k)
  • ROUTING_NOTE ThreadLocal cleared in the compile finally; describeRouting() documented debug-only, capture cleared in finally
  • BackrefBacktrackMatcher per-thread workspace shrinks arena/stack when the last call used <1/4 of capacity; workspace ownership of returned capture arrays documented; memo load-factor safety
  • PrefilteringMatcher required-literal rejection now covers all entry points (match, matchesBounded, matchBounded, findBoundsFrom)

Codegen correctness / robustness:

  • DFAUnrolledBytecodeGenerator: lookup-table emission enforced terminally (generateLookupTables + completeness assert before visitEnd()); per-class 64KB table memory documented
  • DFASwitchBytecodeGenerator: generation-time assertion that boolean/MatchResult bounded flavors emit identical bucket ranges
  • DeterministicChainBytecodeGenerator: documented proof that the remaining retried regions cannot capture (literal/class-only bodies per DeterministicChainInfo)
  • RecursiveDescentBytecodeGenerator: R2 give-back buffer now amortized-doubling (BytecodeUtil.growIterEnd) instead of O(input-length) per call
  • SubsetConstructor: OOM → StateExplosionException and total-deadline clamp shared by both determinization entry points; deadline check on a dedicated counter immune to mixed increments; dead throwing canReachGroupExit removed

Parser / parity:

  • (?iu)/(?iU) \p{...} property paths now reject loudly via checkUnicodeCaseFold; in-class \b stays U+0008 under (?U); escaped ] inside classes no longer corrupts countGroups
  • HybridMatcher.findMatchFrom enforces the leftmost invariant (NFA-half start must equal DFA start, else span re-match fallback)
  • FallbackPatternDetector tests EpsilonNode before LiteralNode at all sites; CharSet BMP-only (?U) limitation documented; stale calibration comments fixed
  • MatchBudgetExceededException contract documented on ReggieMatcher
  • computeRequiredLiteral parses at most twice (was three)

Benchmark / rust lane:

  • IastRegexpBenchmark no longer crashes the whole class when the rust library is unbuilt; MatchOperationBenchmark distinguishes "pattern refused" from "library missing" and gained a native-handle @TearDown; RealCorpusScanBenchmark now fails fast on reggie/JDK oracle divergence
  • Rust shim: // SAFETY: comments citing jni.h indices, rxLastError thread-local diagnostics for compile refusals, production-reuse warning on the 256MB NFA limit, dead find()/embedded main() removed, Windows probing documented

Tests: new ReviewParityFixesTest pins escaped-bracket group counting, (?U)[\b] backspace parity, (?iu)/(?iU) property rejection, prefilter entry-point coverage, workspace-shrink stability and bounded-cache behavior; HybridFreshInstanceTest fails on timed-out workers; RequiredLiteralAuditTest battery mismatches are hard assertions; deadline tests document their process-global property constraint.

Verification: ./gradlew spotlessApply && ./gradlew build green (3198+ tests, 0 failures), cargo check green; implementation cross-check converged in 2 rounds.

Motivation

java.util.regex remains a measurable CPU consumer in regex-heavy backend services, and the earlier re2j migration attempt never landed. This branch makes reggie the fastest option on every measured shape: 13x faster than re2j on matched and 2.3x on no-match at the corpus, and 2-4x ahead of the JDK end-to-end — with JDK-identical semantics enforced by the parity batteries.

Related Issue(s)

None.

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, incl. jacocoVerify)
  • I have added tests for my changes
  • I have updated documentation (if applicable)
  • My commits are signed

Performance Impact

Box-verified (idle 16-core Linux, JMH, same-run controls; full tables in doc/temp/bench-*):

lane reggie rust jdk re2j
corpus matched µs/pair 0.494 0.622 1.805 6.580
corpus no-match µs/pair 0.246 0.662 11.969 5.140

Arc totals vs the 0.3.0 baseline: no-match sweep 15.6ms → 636µs (24x), matched 340µs → 180µs. Fuzz: 15 standing findings, 0 regressions; all four parity batteries at 0 divergences.

Additional Notes

  • Allocation discipline maintained: prefilters and hybrid halves are allocation-free on hot paths.
  • Known remaining gaps (documented in doc/agents-fallback-and-limitations.md): 7 honest refusals (nullable-capture, anchor-in-quantifier, anchor-dilution, empty-class), matched-side span families (PikeVM capture-lists / BitState greedy spans) as the next work item.
  • Follow-ups outside this branch: release.sh minor (0.4.0 publish) and consumer-side rollout PRs now that the engine is ready.

@jbachorik jbachorik added the AI Generated or assisted by AI label Sep 18, 2026
@codecov-commenter

codecov-commenter commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.09262% with 301 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.2%. Comparing base (fd362c2) to head (9771efb).

Files with missing lines Patch % Lines
.../com/datadoghq/reggie/runtime/RuntimeCompiler.java 80.8% 35 Missing and 25 partials ⚠️
...doghq/reggie/codegen/analysis/PatternAnalyzer.java 79.5% 31 Missing and 27 partials ⚠️
.../codegen/codegen/DFAUnrolledBytecodeGenerator.java 79.3% 26 Missing and 6 partials ⚠️
...gen/codegen/RecursiveDescentBytecodeGenerator.java 74.0% 16 Missing and 11 partials ⚠️
...ie/codegen/codegen/DFASwitchBytecodeGenerator.java 94.1% 9 Missing and 9 partials ⚠️
...ggie/codegen/analysis/RequiredLiteralAnalyzer.java 91.7% 6 Missing and 10 partials ⚠️
...adoghq/reggie/runtime/BackrefBacktrackMatcher.java 96.1% 3 Missing and 12 partials ⚠️
...hq/reggie/codegen/automaton/SubsetConstructor.java 88.8% 5 Missing and 8 partials ⚠️
...q/reggie/codegen/codegen/NFABytecodeGenerator.java 89.0% 9 Missing and 4 partials ⚠️
...va/com/datadoghq/reggie/runtime/HybridMatcher.java 36.8% 9 Missing and 3 partials ⚠️
... and 10 more
Additional details and impacted files
@@            Coverage Diff            @@
##              main    #129     +/-   ##
=========================================
+ Coverage     84.9%   85.2%   +0.2%     
  Complexity       1       1             
=========================================
  Files          159     163      +4     
  Lines        46878   48830   +1952     
  Branches      6485    6978    +493     
=========================================
+ Hits         39812   41612   +1800     
- Misses        5108    5207     +99     
- Partials      1958    2011     +53     
Files with missing lines Coverage Δ
...ghq/reggie/codegen/analysis/CaptureProjection.java 60.4% <100.0%> (ø)
...reggie/codegen/analysis/LinearPatternAnalyzer.java 83.3% <100.0%> (ø)
...hq/reggie/codegen/analysis/PatternCategorizer.java 61.1% <100.0%> (+0.2%) ⬆️
.../com/datadoghq/reggie/codegen/ast/LiteralNode.java 100.0% <ø> (ø)
...m/datadoghq/reggie/codegen/ast/RegexModifiers.java 51.9% <100.0%> (+3.9%) ⬆️
...va/com/datadoghq/reggie/codegen/automaton/DFA.java 79.7% <100.0%> (+1.9%) ⬆️
...va/com/datadoghq/reggie/codegen/automaton/NFA.java 91.0% <100.0%> (+0.5%) ⬆️
...oghq/reggie/codegen/automaton/ThompsonBuilder.java 97.5% <100.0%> (+0.3%) ⬆️
...ggie/codegen/codegen/LazyDFABytecodeGenerator.java 90.4% <ø> (ø)
...degen/VariableCaptureBackrefBytecodeGenerator.java 94.2% <100.0%> (-0.1%) ⬇️
... and 26 more

... and 5 files 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 fd362c2...9771efb. Read the comment docs.

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

@jbachorik
jbachorik marked this pull request as ready for review September 18, 2026 12:14
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 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-18T12:18:21.666703Z 293d0e6 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.

@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: 293d0e6b8e

ℹ️ 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 added a commit that referenced this pull request Sep 18, 2026
…129)

Settles the cairn investigation for the dd-backend-check arc into
doc/investigations/backend-ready: 14 nodes, 16 evidence files. Wires the
nine orphaned evidence nodes flagged by the consolidation pass (R1/R2
tranche evidence onto the prefilter hypothesis and the hybrid-admission
question), confirms hyp-unanchored-find-prefilter (arc delivered:
no-match sweep 15.6ms -> 636us, 24x; acceptance gate passed), and
records the JMH method-selector gotcha for rerunning the LdapNoMatch
lane.
@jbachorik
jbachorik force-pushed the feat/dd_backend_check branch from c316dd3 to e8783f2 Compare September 18, 2026 12:22

@datadog-official datadog-official 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.

Datadog Autotest: FAIL

Large counted quantifiers do not have the same behavior in the compile-time and runtime paths. The runtime path also ignores lookaround data and retains nested counters across outer loop iterations.

Open Bits AI session

🤖 Datadog Autotest · Commit 293d0e6 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

Comment thread reggie-runtime/src/main/java/com/datadoghq/reggie/runtime/RuntimeCompiler.java Outdated
Comment thread reggie-runtime/src/main/java/com/datadoghq/reggie/runtime/RuntimeCompiler.java Outdated
@jbachorik
jbachorik force-pushed the feat/dd_backend_check branch from e8783f2 to 25ec2c7 Compare September 18, 2026 12:39
@jbachorik jbachorik changed the title 0.4.0: logs-backend regex replacement readiness — R1/R2 prefilter arc, hybrid engines, JIT sizing, parity batteries 0.4.0: backend regex replacement readiness — literal-prefilter arc, hybrid engines, JIT sizing, parity batteries Sep 18, 2026
Bound compile work for bounded quantifiers (semver hang, OOM bombs),
NUL literal no longer silently dropped (epsilon sentinel collision),
bare '}' is a literal (JDK leniency, unblocks 19 consumer patterns),
groupCount no longer counts '(' inside character classes, optional
capturing groups no longer leak failed-attempt spans, POSIX-style
property classes, CASE_INSENSITIVE for pure-ASCII patterns, and
(?U)/ReggieFlags.UNICODE_CHARACTER_CLASS.
-Dreggie.compile.totalDeadlineMs (default 10s) with the two coverage
gaps closed, huge-charset transitions merged into boolean[] lookup
tables, 2152-check input-side NUL differential audit, spotless
normalization.
Bounded quantifiers x{n,m} lower to counted loops,
BackrefBacktrackMatcher DFS is allocation-free on the hot path; the
counted-loop pattern family reaches re2j parity.
JNI shim (Java-21-safe, marshalling in the timed path),
RustRegexEngine with scan + full-match semantics, buildRustEngine
task, MatchOperationBenchmark lanes; cairn investigation records for
the arc (cost baseline, fusion dead end, rust crossover).
R1: sound AST literal extraction + universal rejection wrapper for
unanchored find() (no-match sweep 9.1x). R2: linear find() for
.*-prefix recursive-descent patterns. R1b: 1-char required facts +
ASCII case-insensitive facts. Alternation-priority conflicts re-route
to PikeVM instead of refusing (refusals 10 -> 7). BytecodeDebugger
reports the actual pipeline routing.
Greedy \Z spans, LazyDFA leftmost start, three real-input parity
fixes (nullable-tail group spans, backref suffix/end-anchor,
parse-refusal fallback bypass) found by the new real-input parity
battery, two \b find() divergences, misplaced-^ anchor guard.
JIT-sized DFA_SWITCH codegen (methods over HugeMethodLimit ran
interpreted), hybrid admission for start-anchored, unanchored and
lazy-aware patterns with RE2 leftmost-first thread pruning,
priority-aware alternation retry, RD give-back hybridization, JIT
method-size gate under ALLOW_JDK_FALLBACK. Corpus matched 340 -> 180us.
…gate

Hybrid nfa-halves context-search from the DFA leftmost start (span
re-match as fallback); findFrom's literal jump is gated on a verified
match prefix; parity corpus extended 513 -> 528 patterns.
…intrinsic

re2j real-corpus lane + rust IAST lanes (the 4-engine benchmark
matrix); 1-char prefilter facts scan with the char indexOf intrinsic
(the String overload's first-char scan is 5.7x slower on x86): IAST
LdapNoMatch LONG 276 -> 2,026 ops/ms, corpus no-match ~9% relative.
@jbachorik
jbachorik force-pushed the feat/dd_backend_check branch from 25ec2c7 to b185c90 Compare September 21, 2026 07:03
Cairn investigation settled into doc/investigations/backend-ready:
14 nodes, 16 evidence files; prefilter hypothesis confirmed (arc
delivered, no-match sweep 24x, acceptance gate passed).
@jbachorik
jbachorik force-pushed the feat/dd_backend_check branch from b185c90 to 9771efb Compare September 21, 2026 10:55
@jbachorik

Copy link
Copy Markdown
Collaborator Author

@arp review

@jbachorik jbachorik changed the title 0.4.0: backend regex replacement readiness — literal-prefilter arc, hybrid engines, JIT sizing, parity batteries 0.4.0: backend regex replacement readiness — literal-prefilter arc, hybrid engines, JIT sizing, parity batteries + review hardening (42 findings addressed) Sep 24, 2026
@jbachorik
jbachorik merged commit e46ee86 into main Sep 24, 2026
9 checks passed
@jbachorik
jbachorik deleted the feat/dd_backend_check branch September 24, 2026 10:45
@jbachorik jbachorik added this to the 0.4.0 milestone Sep 24, 2026
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants