Skip to content

feat: SIMD optimization - #120

Merged
tinovyatkin merged 2 commits into
mainfrom
codex/issue-79-simd-self-loops
Jul 19, 2026
Merged

feat: SIMD optimization#120
tinovyatkin merged 2 commits into
mainfrom
codex/issue-79-simd-self-loops

Conversation

@tinovyatkin

@tinovyatkin tinovyatkin commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

closes #79

Summary by CodeRabbit

  • Performance

    • Improved compiled lexer performance for long ASCII runs by scanning multiple bytes at once.
    • Added performance metrics for bulk scanning, scalar processing, exits, and rejected states.
  • Reliability

    • Strengthened validation and serialization checks for compiled lexer data.
    • Added coverage for ASCII run scanning and serialized data consistency.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@tinovyatkin, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f665cc42-acdb-4be7-84cc-d6250b46f543

📥 Commits

Reviewing files that changed from the base of the PR and between 3db9fde and d044bac.

📒 Files selected for processing (1)
  • src/atn/lexer_dfa.rs
📝 Walkthrough

Walkthrough

The compiled lexer adds exact ASCII self-loop descriptors, serializes them with the DFA, bulk-scans qualifying runs, and records expanded performance metrics. Tests cover descriptor classification, differential scanning, token metadata, counters, and serialization validation.

Changes

Compiled ASCII run scanning

Layer / File(s) Summary
ASCII run descriptors and serialization
src/atn/lexer_dfa.rs
Compiled DFA states classify exact ASCII self-loop patterns, store packed descriptors, serialize and restore them, validate them against dense rows, and reject incompatible serialized data.
Thresholded compiled lexer scanning
src/atn/lexer.rs
The compiled ASCII path detects sufficiently long self-loop prefixes and advances through AsciiRun::scan, updating lexer position, error bounds, accept tracking, and progress counters.
Performance counters and behavioral validation
src/perf.rs, src/atn/lexer_dfa.rs, Cargo.toml
New run-scan metrics and token snapshot fields support tests for scalar equivalence, counter coverage, serialization round trips, descriptor rejection, and the memchr scanning dependency.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Issue 80 — Extends the same AsciiRun infrastructure toward compact ASCII range-class scanning.

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Input
  participant CompiledLexer
  participant CompiledLexerDfa
  participant AsciiRun
  participant PerfCounters
  Input->>CompiledLexer: provide ASCII input
  CompiledLexer->>CompiledLexerDfa: inspect current state run descriptor
  CompiledLexer->>AsciiRun: scan self-loop prefix
  AsciiRun-->>CompiledLexer: return bytes scanned and exit status
  CompiledLexer->>PerfCounters: record run-scan and scalar progress
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: a SIMD-based lexer optimization.
Linked Issues check ✅ Passed The changes implement SIMD skipping for exact compiled-DFA self-loop runs, add serialization, counters, and tests, matching issue #79.
Out of Scope Changes check ✅ Passed The diff stays focused on lexer SIMD optimization, supporting serialization, perf counters, and the new memchr dependency.
Docstring Coverage ✅ Passed Docstring coverage is 82.50% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-79-simd-self-loops

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Copy/Paste Detection

Found 10 duplication(s) across 3 changed Rust file(s) (threshold: 100 tokens).

Show duplications

Found a 54 line (320 tokens) duplication in the following files:

  • Starting at line 486 of src/atn/lexer.rs
  • Starting at line 557 of src/atn/lexer.rs
    atn: &LexerAtn,
    hooks: &mut H,
    mut generated_action: A,
    mut generated_predicate: P,
    unknown_policy: UnknownSemanticPolicy,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    H: SemanticHooks,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction) -> bool,
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> Option<bool>,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    let hooks = RefCell::new(hooks);
    let token = next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut |lexer, action| {
            if !generated_action(lexer, action)
                && !dispatch_lexer_action_hook(&hooks, lexer, action)
                && unknown_policy == UnknownSemanticPolicy::Error
                && let (Ok(rule), Ok(index)) = (
                    usize::try_from(action.rule_index()),
                    usize::try_from(action.action_index()),
                )
            {
                lexer.record_semantic_error(true, rule, index);
            }
        },
        &mut |lexer, predicate| {
            generated_predicate(lexer, predicate)
                .or_else(|| dispatch_lexer_predicate_hook(&hooks, lexer, predicate))
                .unwrap_or_else(|| match unknown_policy {
                    UnknownSemanticPolicy::AssumeTrue => true,
                    UnknownSemanticPolicy::AssumeFalse => false,
                    UnknownSemanticPolicy::Error => {
                        lexer.record_semantic_error(
                            false,
                            predicate.rule_index(),
                            predicate.pred_index(),
                        );
                        false
                    }
                })
        },
        &mut |lexer| dispatch_lexer_before_token_hook(&hooks, lexer),
        &mut accept_adjuster,
        &mut |lexer, accept_position| {
            dispatch_lexer_after_accept_hook(&hooks, lexer, accept_position);
        },
        LexerMatchStrategy {
            compiled: None,

Found a 26 line (153 tokens) duplication in the following files:

  • Starting at line 197 of src/atn/lexer.rs
  • Starting at line 622 of src/atn/lexer.rs
pub fn next_token_with_hooks<I, A, P, E>(
    lexer: &mut BaseLexer<I>,
    sink: &mut TokenSink<'_>,
    atn: &LexerAtn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction),
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut |_| {},
        &mut accept_adjuster,
        &mut |_, _| {},
        LexerMatchStrategy {
            compiled: None,
            use_cache: false,

Found a 25 line (142 tokens) duplication in the following files:

  • Starting at line 330 of src/atn/lexer.rs
  • Starting at line 439 of src/atn/lexer.rs
    atn: &LexerAtn,
    hooks: &mut H,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    H: SemanticHooks,
{
    let hooks = RefCell::new(hooks);
    let token = next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut |lexer, action| {
            let _ = dispatch_lexer_action_hook(&hooks, lexer, action);
        },
        &mut |lexer, predicate| {
            dispatch_lexer_predicate_hook(&hooks, lexer, predicate).unwrap_or(true)
        },
        &mut |lexer| dispatch_lexer_before_token_hook(&hooks, lexer),
        &mut |_, _, _| {},
        &mut |lexer, accept_position| {
            dispatch_lexer_after_accept_hook(&hooks, lexer, accept_position);
        },
        LexerMatchStrategy {
            compiled: None,

Found a 23 line (138 tokens) duplication in the following files:

  • Starting at line 1014 of src/atn/lexer.rs
  • Starting at line 1350 of src/atn/lexer.rs
        let mut next = Vec::new();
        for config in active {
            let Some(state) = atn.state(config.state) else {
                continue;
            };
            for transition in &state.transitions {
                if !transition.matches(symbol, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
                    continue;
                }
                let mut advanced = config.clone();
                set_config_state(atn, &mut advanced, transition.target());
                if symbol == EOF {
                    advanced.consumed_eof = true;
                } else {
                    advanced.position += 1;
                }
                next.push(advanced);
            }
        }

        let closure = epsilon_closure(atn, next, &mut |predicate| {
            semantic_predicate(lexer, predicate)
        });

Found a 24 line (136 tokens) duplication in the following files:

  • Starting at line 897 of src/atn/lexer.rs
  • Starting at line 1014 of src/atn/lexer.rs
        let source_has_semantic_context = dfa_state_has_semantic_context;
        for config in active {
            let Some(state) = atn.state(config.state) else {
                continue;
            };
            for transition in &state.transitions {
                if !transition.matches(symbol, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
                    continue;
                }
                let mut advanced = config.clone();
                set_config_state(atn, &mut advanced, transition.target());
                if symbol == EOF {
                    advanced.consumed_eof = true;
                } else {
                    advanced.position += 1;
                }
                next.push(advanced);
            }
        }

        let closure = epsilon_closure(atn, next, &mut |predicate| {
            semantic_predicate(lexer, predicate)
        });
        let target_has_semantic_context = closure.has_semantic_context;

Found a 24 line (134 tokens) duplication in the following files:

  • Starting at line 2906 of src/atn/lexer.rs
  • Starting at line 1465 of src/atn/lexer_dfa.rs
        let atn = AtnDeserializer::new(&SerializedAtn::from_i32(&[
            4, 0, 2, // version, lexer, max token type
            9, // states
            6, -1, // 0 token start
            2, 0, // 1 rule 0 start
            1, 0, // 2
            1, 0, // 3
            7, 0, // 4 rule 0 stop
            2, 1, // 5 rule 1 start
            1, 1, // 6
            1, 1, // 7
            7, 1, // 8 rule 1 stop
            0, // non-greedy
            0, // precedence
            2, // rules
            1, 1, // rule 0 starts at 1, token type 1
            5, 2, // rule 1 starts at 5, token type 2
            1, // modes
            0, // default mode starts at 0
            0, // sets
            8, // edges
            0, 1, 1, 0, 0, 0, // start -> rule 0
            0, 5, 1, 0, 0, 0, // start -> rule 1
            1, 2, 5, 'a' as i32, 0, 0, 2, 3, 5, 'b' as i32, 0, 0, 3, 4, 1, 0, 0, 0, 5, 6, 5,

Found a 23 line (129 tokens) duplication in the following files:

  • Starting at line 897 of src/atn/lexer.rs
  • Starting at line 1350 of src/atn/lexer.rs
        let source_has_semantic_context = dfa_state_has_semantic_context;
        for config in active {
            let Some(state) = atn.state(config.state) else {
                continue;
            };
            for transition in &state.transitions {
                if !transition.matches(symbol, MIN_CHAR_VALUE, MAX_CHAR_VALUE) {
                    continue;
                }
                let mut advanced = config.clone();
                set_config_state(atn, &mut advanced, transition.target());
                if symbol == EOF {
                    advanced.consumed_eof = true;
                } else {
                    advanced.position += 1;
                }
                next.push(advanced);
            }
        }

        let closure = epsilon_closure(atn, next, &mut |predicate| {
            semantic_predicate(lexer, predicate)
        });

Found a 22 line (117 tokens) duplication in the following files:

  • Starting at line 200 of src/atn/lexer.rs
  • Starting at line 406 of src/atn/lexer.rs
  • Starting at line 625 of src/atn/lexer.rs
    atn: &LexerAtn,
    mut custom_action: A,
    mut semantic_predicate: P,
    mut accept_adjuster: E,
) -> Result<TokenId, TokenStoreError>
where
    I: CharStream,
    A: FnMut(&mut BaseLexer<I>, LexerCustomAction),
    P: FnMut(&BaseLexer<I>, LexerPredicate) -> bool,
    E: FnMut(&mut BaseLexer<I>, i32, usize),
{
    next_token_with_hooks_impl(
        lexer,
        sink,
        atn,
        &mut custom_action,
        &mut semantic_predicate,
        &mut |_| {},
        &mut accept_adjuster,
        &mut |_, _| {},
        LexerMatchStrategy {
            compiled: None,

Found a 13 line (107 tokens) duplication in the following files:

  • Starting at line 2454 of src/atn/lexer.rs
  • Starting at line 2702 of src/atn/lexer.rs
        let mut hooks = LifecycleRecordingHooks::default();
        let mut store = TokenStore::new(lexer.source_text(), lexer.source_name());
        let mut sink = TokenSink::new(&mut store);
        let mut ids = Vec::new();
        for _ in 0..3 {
            let id = if compiled {
                next_token_compiled_with_semantic_hooks(
                    &mut lexer, &mut sink, &atn, &dfa, &mut hooks,
                )
            } else {
                next_token_with_semantic_hooks(&mut lexer, &mut sink, &atn, &mut hooks)
            }
            .expect("lifecycle token should fit");

Found a 14 line (102 tokens) duplication in the following files:

  • Starting at line 1376 of src/atn/lexer_dfa.rs
  • Starting at line 1394 of src/atn/lexer_dfa.rs
        let id = next_token_compiled(lexer, &mut sink, atn, dfa).expect("test token should fit");
        let token = sink.view(id).expect("emitted token should exist");
        TokenSnapshot {
            token_type: token.token_type(),
            text: token.text().to_owned(),
            channel: token.channel(),
            start: token.start(),
            stop: token.stop(),
            start_byte: token.start_byte(),
            stop_byte: token.stop_byte(),
            line: token.line(),
            column: token.column(),
        }
    }

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces fast-scanning loops for ASCII self-loops in the lexer DFA using the memchr crate. It defines an AsciiRun enum to classify self-loop shapes (matching up to three exit characters) and integrates this scanning logic into the compiled lexer. Performance counters and comprehensive unit tests have also been added. The review feedback suggests replacing direct as casts with checked conversions (try_from) when converting indices and lengths to prevent potential silent truncation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/atn/lexer_dfa.rs Outdated
Comment thread src/atn/lexer_dfa.rs Outdated
Comment thread src/atn/lexer_dfa.rs Outdated
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: Konstantin Vyatkin <tino@vtkn.io>
@tinovyatkin
tinovyatkin merged commit c8775c2 into main Jul 19, 2026
12 checks passed
@tinovyatkin
tinovyatkin deleted the codex/issue-79-simd-self-loops branch July 19, 2026 12:39
@ophiarch ophiarch Bot mentioned this pull request Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(lexer): SIMD-skip exact compiled-DFA self-loop runs

1 participant