Take package-shaped repeats and classes out of the per-character phase loop - #3
Conversation
…phase loop Perry's package benchmarks put a sixth of its excess over Node across eleven npm packages in regular expressions, three quarters of that in this matcher, and the patterns doing it were short subjects with counted, folded and lazy repeats: uuid's validate, jws's JWS_REGEX, dotenv's line pattern, date format tokens. Each spent its time in phase round trips per character. - A folded class of at most 256 characters with no property is compiled to its case closure as an unfolded class. Equivalence is symmetric, so this answers every character as folding at match time did, and the class can now use the inline scan and the byte run. - A repeat's owed characters, a bounded allowance and a lazy minimum are walked by the run loop and the byte run, not one per round trip. Charges are unchanged per character. - A lazy repeat's commit extends directly past endpoints its continuation's first consumed instruction rejects, byte-wise over ASCII storage, reaching the state commit, fail, rollback and extend reached. The condition's test is uncharged so a resumed commit does not pay it twice. - An unfolded class whose decision fits the phase's batch is decided in the instruction, examining and charging the same ranges. - The inline class test takes up to sixteen ranges, so `\s` (ten) qualifies. Instructions per search against 0.1.10: uuid validate -70.9%, JWS_REGEX -84.9%, `<(.+?)>` -66.1%, trim -40.9%, format tokens -23.6%, dotenv's line -4.8%; searches none of this reaches move by at most +0.3% (docs/performance.md has the table and the method). `[a-z]/i` is no longer refused by the native emitter, since it is now an unfolded class, so it leaves that test's refusal list.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (11)
💤 Files with no reviewable changes (1)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe changes add compile-time closure for eligible case-insensitive classes, direct decisions for eligible non-folded classes, and quota-bounded scans and lazy skips for repeated atoms. Tests compare optimized paths with general VM behavior, and documentation reports behavior and instruction-count comparisons. ChangesRegex matching
Priority: ⬇️ Low Estimated code review effort: 4 (Complex) | ~45 minutes Change: Refactor Sequence Diagram(s)sequenceDiagram
participant Vm
participant atom_scan
participant atom_commit
participant lazy_skip
participant continuation_probe
Vm->>atom_scan: scan repeated atom within quota and work limits
atom_scan-->>Vm: commit when quota is reached
Vm->>atom_commit: pass available work
atom_commit->>lazy_skip: attempt lazy-repeat extension
lazy_skip->>continuation_probe: inspect the continuation condition
continuation_probe-->>lazy_skip: return probe condition or no supported probe
lazy_skip-->>atom_commit: return skip outcome
Merge Risk: ⚪ Minimal · up to No actionable merge-blocking risk is established; the change is mergeable after normal checks. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Some previously compilable case-insensitive patterns can now fail when callers provide tight compilation limits or storage. Matching equivalence has supporting tests, but the effect on applications that depend on successful compilation is not established. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 45.83% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 24 functions across 6 files. (4 skipped: 4 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Perry's package benchmarks (Perry
benchmarks/packages/PROFILE.md) attribute about 17% of Perry's excess instructions over Node across eleven npm packages to regular expressions, roughly three quarters of that inside this matcher. The patterns doing it are short subjects with counted, folded and lazy repeats: uuid'svalidate, jws'sJWS_REGEX, dotenv's line pattern, date-format tokens. Each paid several phase round trips per character. This PR moves those shapes out of the per-character phase loop. The VM and its answers are unchanged, and so is the charge for every character on the paths it touches.Changes
compiler/classes.rs). A folded class of at most 256 characters with no property compiles to its case closure as an unfolded class, sorted and merged:[0-9a-f]/ibecomes three ranges, and[a-z]/iubecomes[A-Za-zſK]. Case equivalence is symmetric (equivalentswalks one cycle from any member), so the closure accepts exactly the characters that match-time folding accepted. Those classes can now use the inline scan and the byte run.executor/atom.rs). The run loop and the byte run used to serve only a greedy unbounded repeat that had already met its minimum. They now take the whole quota: what the repeat still owes, then a greedy allowance.atom_commit→lazy_skip). This is the lazy counterpart of the existing greedy retreat filter, and it uses the same condition (the continuation's first consumed instruction after itsSAVEs). Where that condition rejects the next character, extending directly reaches the same state that commit, fail, pop, rollback and extend reached. On ASCII storage the skip walks bytes. The condition test is uncharged, because a commit that asks for more frames is resumed and would otherwise pay for it twice. A quantum that runs out part way resumes the same commit.class_decide) whenever the decision fits the class phase's batch. It examines the same ranges in the same order and charges them the same way.ATOM_CLASS_RANGESgoes from 8 to 16, so\s(ten ranges) takes the inline class test in repeats.The docs are updated in
casefold.md,classes.md,repetition.mdandperformance.md, which has the measurement table.Measurements
Instructions per search,
perf stat -e instructions:u. Each figure is the difference between 11,000 and 1,000 warmfinds divided by 10,000. Both arms use the same driver on one idle AMD EPYC 9254, built against3fcc37e(0.1.10) and against this branch:validateregex (/i)iJWS_REGEX, 155-char token^l-\d{1,2}$/i(node-cron)^\d+$(\w+)@(\w+)\.com[a-z]+[0-9]+\w+!over 60 chars(?<=\$)\d+<(.+?)>^\s+|\s+$(\w)\1g\p{L}+/u%..|.(split piece)needleneedle/icat|dog|birdafter 200 charszzzover 1,000 chars (miss)The five
+0.x%rows go through paths this PR does not change. They move by 3–5 instructions per search, and I did not isolate where those come from.In Perry on the same host, built against this branch through a local
[patch.crates-io], with both arms built the same way and no auto-optimize:REGEX.test: 23,500 → 8,225 instructions per callJWS_REGEX.test: 96,711 → 15,394^\d+$test: 4,453 → 4,163execloop over a 36-line document: 1,609,585 → 1,559,285Checks run (all on this branch)
cargo fmt --check,cargo check --all-targets,cargo clippy --all-targets -D warningscargo test --lockedandcargo test --locked --release: all pass--quantum 1|17 --relocate --grow), modifiers, unicode-sets, sets, properties, input,reference.mjs --check,reference.test.mjs, andcheck-test262.mjsagainst test2624249661388e5folded_class_closure_matches_match_time_folding(tests/classes.rs) compares each closed class against the same class plus the Private Use Area, which is too large to close and has no case mappings. It covers plain and negated classes,iandiu, the BMP's cased blocks and astral cased blocks.counted_runs_and_lazy_skips_keep_general_vm_captures_and_errors(tests/atom_repeat.rs) runs 14 patterns × 4 flag sets × 25 subjects × byte/UTF-16 storage against the general repetition instructions, and checks every insufficient work allowance.U+212Afrom the closure, and letting the lazy skip pass akcontinuation, each makes its test fail.Notes
refuses_what_it_cannot_generateno longer lists/[a-z]/i. It now compiles to an unfolded class, which the native emitter accepts.\s-sized classes in repeats, which are now charged per class rather than per range visited. Paused and unpaused searches still reach the same total; the resumption tests and the q1/q17 harness runs cover that.perex-benchagainst V8 wall-clock (bench/compare.sh). The figures above are instruction counts, not CPU time.RunErrorneeds a small Perry adaptation, which is ready on Perry branchwip/perf-regex-matcher, and the release then has to clear Perry's 7-daymin-publish-agesoak.Summary by CodeRabbit