Skip to main content

antlr4_runtime/
dfa.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3use crate::prediction::{AtnConfigSet, ContextArena, ContextId, PredictionFxHasher};
4use std::collections::HashMap;
5use std::hash::BuildHasherDefault;
6use std::mem::size_of;
7
8type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
9
10const NO_EDGE_INDEX: u32 = u32::MAX;
11const NO_PREDICTION: u32 = u32::MAX;
12const ACCEPT_STATE: u8 = 1 << 0;
13const REQUIRES_FULL_CONTEXT: u8 = 1 << 1;
14const HAS_SEMANTIC_CONTEXT: u8 = 1 << 2;
15
16// Across the protected Kotlin, C#, Java, and Trino fixtures, rows with fewer
17// than eight edges were overwhelmingly sparse. Above that point, dense rows
18// won once at least one eighth of a bounded vocabulary was populated.
19const DENSE_MAX_ROW_WIDTH: u32 = 512;
20const DENSE_MIN_EDGES: u32 = 8;
21const DENSE_DENSITY_DENOMINATOR: u32 = 8;
22
23fn compact_index(index: usize, message: &'static str) -> u32 {
24    u32::try_from(index)
25        .ok()
26        .filter(|value| *value != u32::MAX)
27        .expect(message)
28}
29
30/// Compact identity for one learned parser-DFA state.
31#[repr(transparent)]
32#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub struct DfaStateId(u32);
34
35pub(crate) const NO_DFA_STATE: DfaStateId = DfaStateId(u32::MAX);
36
37impl DfaStateId {
38    fn from_index(index: usize) -> Self {
39        Self(compact_index(
40            index,
41            "parser DFA state count must fit below the u32 sentinel",
42        ))
43    }
44
45    /// Returns this state's zero-based diagnostic index.
46    pub fn index(self) -> usize {
47        usize::try_from(self.0).expect("u32 DFA state ID fits in usize")
48    }
49}
50
51/// Read-only transition exposed by parser-DFA diagnostics.
52#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub struct DfaTransition {
54    pub source: DfaStateId,
55    pub symbol: i32,
56    pub target: DfaStateId,
57}
58
59/// Storage and learning measurements for one parser DFA.
60#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
61pub struct ParserDfaStats {
62    pub states: usize,
63    pub transitions: usize,
64    pub max_row_width: usize,
65    pub dense_rows: usize,
66    pub sparse_rows: usize,
67    pub empty_rows: usize,
68    pub dense_slots: usize,
69    pub sparse_entries: usize,
70    /// Widths <=64, <=128, <=256, <=512, and >512.
71    pub row_width_histogram: [usize; 5],
72    /// Populated-edge counts 0, 1, 2-3, 4-7, 8-15, and >=16.
73    pub populated_edge_histogram: [usize; 6],
74    /// Empty, <=1%, <=5%, <=12.5%, <=25%, and >25% populated rows.
75    pub edge_density_histogram: [usize; 6],
76    pub hot_bytes: usize,
77    pub cold_bytes: usize,
78    pub states_created: usize,
79    pub states_deduplicated: usize,
80    pub fingerprint_candidates: usize,
81    pub fingerprint_collisions: usize,
82}
83
84impl ParserDfaStats {
85    pub(crate) fn add_assign(&mut self, other: Self) {
86        self.states = self.states.saturating_add(other.states);
87        self.transitions = self.transitions.saturating_add(other.transitions);
88        self.max_row_width = self.max_row_width.max(other.max_row_width);
89        self.dense_rows = self.dense_rows.saturating_add(other.dense_rows);
90        self.sparse_rows = self.sparse_rows.saturating_add(other.sparse_rows);
91        self.empty_rows = self.empty_rows.saturating_add(other.empty_rows);
92        self.dense_slots = self.dense_slots.saturating_add(other.dense_slots);
93        self.sparse_entries = self.sparse_entries.saturating_add(other.sparse_entries);
94        for (total, value) in self
95            .row_width_histogram
96            .iter_mut()
97            .zip(other.row_width_histogram)
98        {
99            *total = total.saturating_add(value);
100        }
101        for (total, value) in self
102            .populated_edge_histogram
103            .iter_mut()
104            .zip(other.populated_edge_histogram)
105        {
106            *total = total.saturating_add(value);
107        }
108        for (total, value) in self
109            .edge_density_histogram
110            .iter_mut()
111            .zip(other.edge_density_histogram)
112        {
113            *total = total.saturating_add(value);
114        }
115        self.hot_bytes = self.hot_bytes.saturating_add(other.hot_bytes);
116        self.cold_bytes = self.cold_bytes.saturating_add(other.cold_bytes);
117        self.states_created = self.states_created.saturating_add(other.states_created);
118        self.states_deduplicated = self
119            .states_deduplicated
120            .saturating_add(other.states_deduplicated);
121        self.fingerprint_candidates = self
122            .fingerprint_candidates
123            .saturating_add(other.fingerprint_candidates);
124        self.fingerprint_collisions = self
125            .fingerprint_collisions
126            .saturating_add(other.fingerprint_collisions);
127    }
128}
129
130/// Opaque learned parser DFA with split hot transition and cold ATN-config data.
131#[derive(Debug)]
132pub struct ParserDfa {
133    decision: usize,
134    atn_start_state: usize,
135    max_token_type: i32,
136    hot: DfaHotTables,
137    cold: DfaColdStore,
138    interner: DfaStateInterner,
139    start_state: DfaStateId,
140    precedence_start_states: Vec<DfaStateId>,
141    precedence_mode: bool,
142    learning_revision: u64,
143    learning: DfaLearningCounters,
144}
145
146impl ParserDfa {
147    pub fn new(atn_start_state: usize, decision: usize) -> Self {
148        Self::with_max_token_type(atn_start_state, decision, 0)
149    }
150
151    pub fn with_max_token_type(
152        atn_start_state: usize,
153        decision: usize,
154        max_token_type: i32,
155    ) -> Self {
156        Self {
157            decision,
158            atn_start_state,
159            max_token_type,
160            hot: DfaHotTables::new(max_token_type),
161            cold: DfaColdStore::default(),
162            interner: DfaStateInterner::default(),
163            start_state: NO_DFA_STATE,
164            precedence_start_states: Vec::new(),
165            precedence_mode: false,
166            learning_revision: 0,
167            learning: DfaLearningCounters::default(),
168        }
169    }
170
171    pub const fn decision(&self) -> usize {
172        self.decision
173    }
174
175    pub const fn atn_start_state(&self) -> usize {
176        self.atn_start_state
177    }
178
179    pub const fn max_token_type(&self) -> i32 {
180        self.max_token_type
181    }
182
183    pub const fn state_count(&self) -> usize {
184        self.hot.len()
185    }
186
187    pub const fn is_empty(&self) -> bool {
188        self.hot.is_empty()
189    }
190
191    pub fn states(&self) -> impl ExactSizeIterator<Item = ParserDfaStateView<'_>> {
192        (0..self.state_count()).map(|index| ParserDfaStateView {
193            dfa: self,
194            id: DfaStateId::from_index(index),
195        })
196    }
197
198    pub fn state(&self, id: DfaStateId) -> Option<ParserDfaStateView<'_>> {
199        (id.index() < self.state_count()).then_some(ParserDfaStateView { dfa: self, id })
200    }
201
202    pub fn transitions(&self) -> impl Iterator<Item = DfaTransition> + '_ {
203        self.states().flat_map(ParserDfaStateView::transitions)
204    }
205
206    pub fn start_state(&self) -> Option<DfaStateId> {
207        (self.start_state != NO_DFA_STATE).then_some(self.start_state)
208    }
209
210    pub(crate) fn set_start_state(&mut self, state: DfaStateId) {
211        self.assert_valid_state(state);
212        if self.start_state == state {
213            return;
214        }
215        self.start_state = state;
216        self.bump_learning_revision();
217    }
218
219    pub const fn is_precedence_dfa(&self) -> bool {
220        self.precedence_mode
221    }
222
223    pub(crate) fn set_precedence_dfa(&mut self, precedence_dfa: bool) {
224        if self.precedence_mode == precedence_dfa {
225            return;
226        }
227        self.hot.clear();
228        self.cold.clear();
229        self.interner.clear();
230        self.start_state = NO_DFA_STATE;
231        self.precedence_start_states.clear();
232        self.precedence_mode = precedence_dfa;
233        self.bump_learning_revision();
234        if precedence_dfa {
235            let state = self.add_state(DfaStateBuilder::new(AtnConfigSet::new()));
236            self.start_state = state;
237        }
238    }
239
240    pub fn precedence_start_state(&self, precedence: usize) -> Option<DfaStateId> {
241        self.precedence_start_states
242            .get(precedence)
243            .copied()
244            .filter(|state| *state != NO_DFA_STATE)
245    }
246
247    pub(crate) fn precedence_start_states(&self) -> &[DfaStateId] {
248        &self.precedence_start_states
249    }
250
251    pub(crate) fn set_precedence_start_state(&mut self, precedence: usize, state: DfaStateId) {
252        self.assert_valid_state(state);
253        if self.precedence_start_state(precedence) == Some(state) {
254            return;
255        }
256        if precedence >= self.precedence_start_states.len() {
257            self.precedence_start_states
258                .resize(precedence + 1, NO_DFA_STATE);
259        }
260        self.precedence_start_states[precedence] = state;
261        self.bump_learning_revision();
262    }
263
264    pub(crate) const fn learning_revision(&self) -> u64 {
265        self.learning_revision
266    }
267
268    pub fn stats(&self) -> ParserDfaStats {
269        let edge_stats = self.hot.edges.stats();
270        ParserDfaStats {
271            states: self.state_count(),
272            transitions: edge_stats.transitions,
273            max_row_width: edge_stats.max_row_width,
274            dense_rows: edge_stats.dense_rows,
275            sparse_rows: edge_stats.sparse_rows,
276            empty_rows: edge_stats.empty_rows,
277            dense_slots: edge_stats.dense_slots,
278            sparse_entries: edge_stats.sparse_entries,
279            row_width_histogram: edge_stats.row_width_histogram,
280            populated_edge_histogram: edge_stats.populated_edge_histogram,
281            edge_density_histogram: edge_stats.edge_density_histogram,
282            hot_bytes: self.hot.retained_bytes()
283                + self.interner.retained_bytes()
284                + self.precedence_start_states.capacity() * size_of::<DfaStateId>(),
285            cold_bytes: self.cold.retained_bytes(),
286            states_created: self.learning.states_created,
287            states_deduplicated: self.learning.states_deduplicated,
288            fingerprint_candidates: self.learning.fingerprint_candidates,
289            fingerprint_collisions: self.learning.fingerprint_collisions,
290        }
291    }
292
293    pub(crate) fn add_state(&mut self, state: DfaStateBuilder) -> DfaStateId {
294        let fingerprint = state.configs.fingerprint();
295        if let Some(existing) = self.find_state(fingerprint, &state.configs) {
296            self.learning.states_deduplicated = self.learning.states_deduplicated.saturating_add(1);
297            #[cfg(feature = "perf-counters")]
298            crate::perf::record_dfa_state_deduplicated();
299            return existing;
300        }
301        self.insert_state_with_fingerprint(state, fingerprint)
302    }
303
304    pub(crate) fn insert_state(&mut self, state: DfaStateBuilder) -> DfaStateId {
305        let fingerprint = state.configs.fingerprint();
306        self.insert_state_with_fingerprint(state, fingerprint)
307    }
308
309    fn insert_state_with_fingerprint(
310        &mut self,
311        state: DfaStateBuilder,
312        fingerprint: u64,
313    ) -> DfaStateId {
314        let id = DfaStateId::from_index(self.state_count());
315        let DfaStateBuilder {
316            mut configs,
317            prediction,
318            requires_full_context,
319            conflicting_alts,
320            has_semantic_context_for_alt,
321        } = state;
322        configs.set_readonly(true);
323        self.hot.push_state(
324            prediction,
325            requires_full_context,
326            has_semantic_context_for_alt,
327        );
328        self.cold.push(configs, conflicting_alts);
329        self.interner.insert(fingerprint, id);
330        self.learning.states_created = self.learning.states_created.saturating_add(1);
331        self.bump_learning_revision();
332        #[cfg(feature = "perf-counters")]
333        crate::perf::record_dfa_state_created();
334        id
335    }
336
337    pub(crate) fn state_id_for_configs(&mut self, configs: &AtnConfigSet) -> Option<DfaStateId> {
338        let state = self.find_state(configs.fingerprint(), configs);
339        if state.is_some() {
340            self.learning.states_deduplicated = self.learning.states_deduplicated.saturating_add(1);
341            #[cfg(feature = "perf-counters")]
342            crate::perf::record_dfa_state_deduplicated();
343        }
344        state
345    }
346
347    fn find_state(&mut self, fingerprint: u64, configs: &AtnConfigSet) -> Option<DfaStateId> {
348        let mut candidate = self.interner.head(fingerprint);
349        while candidate != NO_DFA_STATE {
350            self.learning.fingerprint_candidates =
351                self.learning.fingerprint_candidates.saturating_add(1);
352            #[cfg(feature = "perf-counters")]
353            crate::perf::record_dfa_fingerprint_candidate();
354            if self.configs(candidate) == configs {
355                return Some(candidate);
356            }
357            self.learning.fingerprint_collisions =
358                self.learning.fingerprint_collisions.saturating_add(1);
359            #[cfg(feature = "perf-counters")]
360            crate::perf::record_dfa_fingerprint_collision();
361            candidate = self.interner.next(candidate);
362        }
363        None
364    }
365
366    pub(crate) fn edge(&self, source: DfaStateId, symbol: i32) -> Option<DfaStateId> {
367        self.hot.edges.target(source, symbol)
368    }
369
370    pub(crate) fn add_edge(&mut self, source: DfaStateId, symbol: i32, target: DfaStateId) {
371        self.assert_valid_state(source);
372        self.assert_valid_state(target);
373        let previous = self.edge(source, symbol);
374        self.hot.edges.add(source, symbol, target);
375        if self.edge(source, symbol) != previous {
376            self.bump_learning_revision();
377        }
378    }
379
380    pub(crate) fn configs(&self, state: DfaStateId) -> &AtnConfigSet {
381        &self.cold.configs[state.index()]
382    }
383
384    pub(crate) fn conflicting_alts(&self, state: DfaStateId) -> &[usize] {
385        &self.cold.extras[state.index()].conflicting_alts
386    }
387
388    pub(crate) fn clone_state_without_edges(&self, state: DfaStateId) -> DfaStateBuilder {
389        let index = state.index();
390        DfaStateBuilder {
391            configs: self.cold.configs[index].clone(),
392            prediction: self.hot.prediction(state),
393            requires_full_context: self.hot.has_flag(state, REQUIRES_FULL_CONTEXT),
394            conflicting_alts: self.cold.extras[index].conflicting_alts.clone(),
395            has_semantic_context_for_alt: self.hot.has_flag(state, HAS_SEMANTIC_CONTEXT),
396        }
397    }
398
399    pub(crate) fn remap_contexts(&mut self, remap: &[ContextId], arena: &ContextArena) {
400        for configs in &mut self.cold.configs {
401            configs.remap_contexts(remap, arena);
402        }
403        self.interner.rebuild(&self.cold.configs);
404    }
405
406    fn assert_valid_state(&self, state: DfaStateId) {
407        assert_ne!(state, NO_DFA_STATE, "DFA state ID cannot be the sentinel");
408        assert!(
409            state.index() < self.state_count(),
410            "DFA state ID must index aligned hot/cold storage"
411        );
412    }
413
414    const fn bump_learning_revision(&mut self) {
415        self.learning_revision = self.learning_revision.wrapping_add(1);
416    }
417}
418
419/// Borrowing diagnostic view over one parser-DFA state.
420#[derive(Clone, Copy)]
421pub struct ParserDfaStateView<'a> {
422    dfa: &'a ParserDfa,
423    id: DfaStateId,
424}
425
426impl std::fmt::Debug for ParserDfaStateView<'_> {
427    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        formatter
429            .debug_struct("ParserDfaStateView")
430            .field("id", &self.id)
431            .field("is_accept_state", &self.is_accept_state())
432            .field("prediction", &self.prediction())
433            .field("requires_full_context", &self.requires_full_context())
434            .finish_non_exhaustive()
435    }
436}
437
438impl<'a> ParserDfaStateView<'a> {
439    pub const fn id(self) -> DfaStateId {
440        self.id
441    }
442
443    pub fn is_accept_state(self) -> bool {
444        self.dfa.hot.has_flag(self.id, ACCEPT_STATE)
445    }
446
447    pub fn prediction(self) -> Option<usize> {
448        self.dfa.hot.prediction(self.id)
449    }
450
451    pub fn requires_full_context(self) -> bool {
452        self.dfa.hot.has_flag(self.id, REQUIRES_FULL_CONTEXT)
453    }
454
455    pub fn has_semantic_context(self) -> bool {
456        self.dfa.hot.has_flag(self.id, HAS_SEMANTIC_CONTEXT)
457    }
458
459    pub fn edge(self, symbol: i32) -> Option<DfaStateId> {
460        self.dfa.edge(self.id, symbol)
461    }
462
463    pub fn transitions(self) -> impl Iterator<Item = DfaTransition> + 'a {
464        self.dfa
465            .hot
466            .edges
467            .transitions(self.id)
468            .map(move |(symbol, target)| DfaTransition {
469                source: self.id,
470                symbol,
471                target,
472            })
473    }
474}
475
476#[derive(Debug)]
477pub(crate) struct DfaStateBuilder {
478    pub(crate) configs: AtnConfigSet,
479    prediction: Option<usize>,
480    requires_full_context: bool,
481    conflicting_alts: Vec<usize>,
482    has_semantic_context_for_alt: bool,
483}
484
485impl DfaStateBuilder {
486    pub(crate) const fn new(configs: AtnConfigSet) -> Self {
487        Self {
488            configs,
489            prediction: None,
490            requires_full_context: false,
491            conflicting_alts: Vec::new(),
492            has_semantic_context_for_alt: false,
493        }
494    }
495
496    pub(crate) const fn mark_accept(&mut self, prediction: usize) {
497        self.prediction = Some(prediction);
498    }
499
500    pub(crate) const fn set_requires_full_context(&mut self, required: bool) {
501        self.requires_full_context = required;
502    }
503
504    pub(crate) fn set_conflicting_alts(&mut self, conflicting_alts: Vec<usize>) {
505        self.conflicting_alts = conflicting_alts;
506    }
507
508    pub(crate) const fn set_has_semantic_context_for_alt(&mut self, has_semantic: bool) {
509        self.has_semantic_context_for_alt = has_semantic;
510    }
511}
512
513#[derive(Debug)]
514struct DfaHotTables {
515    edges: EdgeTable,
516    accept_predictions: Vec<u32>,
517    flags: Vec<u8>,
518}
519
520impl DfaHotTables {
521    fn new(max_token_type: i32) -> Self {
522        Self {
523            edges: EdgeTable::new(max_token_type),
524            accept_predictions: Vec::new(),
525            flags: Vec::new(),
526        }
527    }
528
529    fn push_state(
530        &mut self,
531        prediction: Option<usize>,
532        requires_full_context: bool,
533        has_semantic_context: bool,
534    ) {
535        self.edges.push_row();
536        let prediction = prediction.map_or(NO_PREDICTION, |prediction| {
537            compact_index(prediction, "DFA prediction must fit below the u32 sentinel")
538        });
539        self.accept_predictions.push(prediction);
540        let mut flags = 0;
541        if prediction != NO_PREDICTION {
542            flags |= ACCEPT_STATE;
543        }
544        if requires_full_context {
545            flags |= REQUIRES_FULL_CONTEXT;
546        }
547        if has_semantic_context {
548            flags |= HAS_SEMANTIC_CONTEXT;
549        }
550        self.flags.push(flags);
551        debug_assert_eq!(self.edges.len(), self.accept_predictions.len());
552        debug_assert_eq!(self.edges.len(), self.flags.len());
553    }
554
555    fn prediction(&self, state: DfaStateId) -> Option<usize> {
556        let prediction = self.accept_predictions[state.index()];
557        (prediction != NO_PREDICTION)
558            .then(|| usize::try_from(prediction).expect("u32 DFA prediction fits in usize"))
559    }
560
561    fn has_flag(&self, state: DfaStateId, flag: u8) -> bool {
562        self.flags[state.index()] & flag != 0
563    }
564
565    const fn len(&self) -> usize {
566        self.flags.len()
567    }
568
569    const fn is_empty(&self) -> bool {
570        self.flags.is_empty()
571    }
572
573    fn clear(&mut self) {
574        self.edges.clear();
575        self.accept_predictions.clear();
576        self.flags.clear();
577    }
578
579    const fn retained_bytes(&self) -> usize {
580        self.edges.retained_bytes()
581            + self.accept_predictions.capacity() * size_of::<u32>()
582            + self.flags.capacity() * size_of::<u8>()
583    }
584}
585
586#[derive(Debug, Default)]
587struct DfaColdStore {
588    configs: Vec<AtnConfigSet>,
589    extras: Vec<DfaColdExtras>,
590}
591
592impl DfaColdStore {
593    fn push(&mut self, configs: AtnConfigSet, conflicting_alts: Vec<usize>) {
594        self.configs.push(configs);
595        self.extras.push(DfaColdExtras { conflicting_alts });
596        debug_assert_eq!(self.configs.len(), self.extras.len());
597    }
598
599    fn clear(&mut self) {
600        self.configs.clear();
601        self.extras.clear();
602    }
603
604    fn retained_bytes(&self) -> usize {
605        self.configs.capacity() * size_of::<AtnConfigSet>()
606            + self.extras.capacity() * size_of::<DfaColdExtras>()
607            + self
608                .configs
609                .iter()
610                .map(AtnConfigSet::retained_bytes)
611                .sum::<usize>()
612            + self
613                .extras
614                .iter()
615                .map(|extra| extra.conflicting_alts.capacity() * size_of::<usize>())
616                .sum::<usize>()
617    }
618}
619
620#[derive(Debug, Default)]
621struct DfaColdExtras {
622    conflicting_alts: Vec<usize>,
623}
624
625#[derive(Debug, Default)]
626struct DfaStateInterner {
627    heads: FxHashMap<u64, DfaStateId>,
628    next: Vec<DfaStateId>,
629}
630
631impl DfaStateInterner {
632    fn head(&self, fingerprint: u64) -> DfaStateId {
633        self.heads
634            .get(&fingerprint)
635            .copied()
636            .unwrap_or(NO_DFA_STATE)
637    }
638
639    fn next(&self, state: DfaStateId) -> DfaStateId {
640        self.next[state.index()]
641    }
642
643    fn insert(&mut self, fingerprint: u64, state: DfaStateId) {
644        debug_assert_eq!(state.index(), self.next.len());
645        let previous = self
646            .heads
647            .insert(fingerprint, state)
648            .unwrap_or(NO_DFA_STATE);
649        self.next.push(previous);
650    }
651
652    fn rebuild(&mut self, configs: &[AtnConfigSet]) {
653        self.clear();
654        self.next.reserve(configs.len());
655        for (index, configs) in configs.iter().enumerate() {
656            self.insert(configs.fingerprint(), DfaStateId::from_index(index));
657        }
658    }
659
660    fn clear(&mut self) {
661        self.heads.clear();
662        self.next.clear();
663    }
664
665    fn retained_bytes(&self) -> usize {
666        self.heads.capacity() * size_of::<(u64, DfaStateId)>()
667            + self.next.capacity() * size_of::<DfaStateId>()
668    }
669}
670
671#[derive(Clone, Copy, Debug, Default)]
672struct DfaLearningCounters {
673    states_created: usize,
674    states_deduplicated: usize,
675    fingerprint_candidates: usize,
676    fingerprint_collisions: usize,
677}
678
679#[derive(Clone, Copy, Debug, Default)]
680enum EdgeRow {
681    #[default]
682    Empty,
683    Inline {
684        symbol: i32,
685        target: DfaStateId,
686    },
687    Sparse {
688        head: u32,
689        len: u32,
690    },
691    Dense {
692        start: u32,
693        populated: u32,
694    },
695}
696
697#[derive(Clone, Copy, Debug)]
698struct SparseEdge {
699    symbol: i32,
700    target: DfaStateId,
701    next: u32,
702}
703
704#[derive(Debug)]
705struct EdgeTable {
706    width: u32,
707    rows: Vec<EdgeRow>,
708    dense_targets: Vec<DfaStateId>,
709    sparse_edges: Vec<SparseEdge>,
710}
711
712impl EdgeTable {
713    fn new(max_token_type: i32) -> Self {
714        let width = i64::from(max_token_type)
715            .checked_add(2)
716            .and_then(|value| u32::try_from(value).ok())
717            .unwrap_or(0);
718        Self {
719            width,
720            rows: Vec::new(),
721            dense_targets: Vec::new(),
722            sparse_edges: Vec::new(),
723        }
724    }
725
726    fn push_row(&mut self) {
727        self.rows.push(EdgeRow::default());
728    }
729
730    const fn len(&self) -> usize {
731        self.rows.len()
732    }
733
734    fn target(&self, state: DfaStateId, symbol: i32) -> Option<DfaStateId> {
735        let slot = self.slot(symbol)?;
736        match *self.rows.get(state.index())? {
737            EdgeRow::Empty => None,
738            EdgeRow::Inline {
739                symbol: stored,
740                target,
741            } => (stored == symbol).then_some(target),
742            EdgeRow::Dense { start, .. } => {
743                let index = usize::try_from(start.checked_add(slot)?).ok()?;
744                self.dense_targets
745                    .get(index)
746                    .copied()
747                    .filter(|target| *target != NO_DFA_STATE)
748            }
749            EdgeRow::Sparse { mut head, .. } => {
750                while head != NO_EDGE_INDEX {
751                    let edge = self.sparse_edges[usize::try_from(head).ok()?];
752                    match edge.symbol.cmp(&symbol) {
753                        std::cmp::Ordering::Less => head = edge.next,
754                        std::cmp::Ordering::Equal => return Some(edge.target),
755                        std::cmp::Ordering::Greater => return None,
756                    }
757                }
758                None
759            }
760        }
761    }
762
763    fn add(&mut self, state: DfaStateId, symbol: i32, target: DfaStateId) {
764        let Some(slot) = self.slot(symbol) else {
765            return;
766        };
767        match self.rows[state.index()] {
768            EdgeRow::Empty => {
769                self.rows[state.index()] = EdgeRow::Inline { symbol, target };
770            }
771            EdgeRow::Inline {
772                symbol: stored_symbol,
773                target: stored_target,
774            } => {
775                if stored_symbol == symbol {
776                    self.rows[state.index()] = EdgeRow::Inline { symbol, target };
777                    return;
778                }
779                self.promote_inline_to_sparse(state, stored_symbol, stored_target, symbol, target);
780            }
781            EdgeRow::Dense { start, populated } => {
782                let index = usize::try_from(start.checked_add(slot).expect("dense slot overflow"))
783                    .expect("u32 dense slot fits in usize");
784                if self.dense_targets[index] == NO_DFA_STATE {
785                    self.rows[state.index()] = EdgeRow::Dense {
786                        start,
787                        populated: populated
788                            .checked_add(1)
789                            .expect("dense edge count must fit in u32"),
790                    };
791                }
792                self.dense_targets[index] = target;
793            }
794            EdgeRow::Sparse { head, len } => {
795                if self.update_sparse(head, symbol, target).is_some() {
796                    return;
797                }
798                self.insert_sparse(state, head, len, symbol, target);
799            }
800        }
801    }
802
803    fn promote_inline_to_sparse(
804        &mut self,
805        state: DfaStateId,
806        first_symbol: i32,
807        first_target: DfaStateId,
808        second_symbol: i32,
809        second_target: DfaStateId,
810    ) {
811        let (lower_symbol, lower_target, upper_symbol, upper_target) =
812            if first_symbol < second_symbol {
813                (first_symbol, first_target, second_symbol, second_target)
814            } else {
815                (second_symbol, second_target, first_symbol, first_target)
816            };
817        let upper_index = compact_index(
818            self.sparse_edges.len(),
819            "sparse edge pool must fit below the u32 sentinel",
820        );
821        self.sparse_edges.push(SparseEdge {
822            symbol: upper_symbol,
823            target: upper_target,
824            next: NO_EDGE_INDEX,
825        });
826        let lower_index = compact_index(
827            self.sparse_edges.len(),
828            "sparse edge pool must fit below the u32 sentinel",
829        );
830        self.sparse_edges.push(SparseEdge {
831            symbol: lower_symbol,
832            target: lower_target,
833            next: upper_index,
834        });
835        self.rows[state.index()] = EdgeRow::Sparse {
836            head: lower_index,
837            len: 2,
838        };
839    }
840
841    fn update_sparse(
842        &mut self,
843        mut edge_index: u32,
844        symbol: i32,
845        target: DfaStateId,
846    ) -> Option<bool> {
847        while edge_index != NO_EDGE_INDEX {
848            let edge = &mut self.sparse_edges
849                [usize::try_from(edge_index).expect("u32 sparse edge index fits in usize")];
850            match edge.symbol.cmp(&symbol) {
851                std::cmp::Ordering::Less => edge_index = edge.next,
852                std::cmp::Ordering::Equal => {
853                    let changed = edge.target != target;
854                    edge.target = target;
855                    return Some(changed);
856                }
857                std::cmp::Ordering::Greater => return None,
858            }
859        }
860        None
861    }
862
863    fn insert_sparse(
864        &mut self,
865        state: DfaStateId,
866        head: u32,
867        len: u32,
868        symbol: i32,
869        target: DfaStateId,
870    ) {
871        let new_index = compact_index(
872            self.sparse_edges.len(),
873            "sparse edge pool must fit below the u32 sentinel",
874        );
875        let mut previous = NO_EDGE_INDEX;
876        let mut current = head;
877        while current != NO_EDGE_INDEX {
878            let edge = self.sparse_edges
879                [usize::try_from(current).expect("u32 sparse edge index fits in usize")];
880            if edge.symbol > symbol {
881                break;
882            }
883            previous = current;
884            current = edge.next;
885        }
886        self.sparse_edges.push(SparseEdge {
887            symbol,
888            target,
889            next: current,
890        });
891        let new_len = len
892            .checked_add(1)
893            .expect("sparse edge count must fit in u32");
894        if previous == NO_EDGE_INDEX {
895            self.rows[state.index()] = EdgeRow::Sparse {
896                head: new_index,
897                len: new_len,
898            };
899        } else {
900            self.sparse_edges
901                [usize::try_from(previous).expect("u32 sparse edge index fits in usize")]
902            .next = new_index;
903            self.rows[state.index()] = EdgeRow::Sparse { head, len: new_len };
904        }
905        if self.should_promote(new_len) {
906            self.promote(state);
907        }
908    }
909
910    const fn should_promote(&self, populated: u32) -> bool {
911        self.width <= DENSE_MAX_ROW_WIDTH
912            && populated >= DENSE_MIN_EDGES
913            && populated.saturating_mul(DENSE_DENSITY_DENOMINATOR) >= self.width
914    }
915
916    fn promote(&mut self, state: DfaStateId) {
917        let EdgeRow::Sparse {
918            mut head,
919            len: populated,
920        } = self.rows[state.index()]
921        else {
922            return;
923        };
924        let start = compact_index(
925            self.dense_targets.len(),
926            "dense edge pool must fit below the u32 sentinel",
927        );
928        let new_len = self
929            .dense_targets
930            .len()
931            .checked_add(usize::try_from(self.width).expect("u32 row width fits in usize"))
932            .expect("dense edge pool length overflow");
933        self.dense_targets.resize(new_len, NO_DFA_STATE);
934        while head != NO_EDGE_INDEX {
935            let edge = self.sparse_edges
936                [usize::try_from(head).expect("u32 sparse edge index fits in usize")];
937            let slot = self.slot(edge.symbol).expect("stored symbol fits row");
938            let index = usize::try_from(start.checked_add(slot).expect("dense slot overflow"))
939                .expect("u32 dense slot fits in usize");
940            self.dense_targets[index] = edge.target;
941            head = edge.next;
942        }
943        self.rows[state.index()] = EdgeRow::Dense { start, populated };
944    }
945
946    fn transitions(&self, state: DfaStateId) -> EdgeTransitions<'_> {
947        match self.rows[state.index()] {
948            EdgeRow::Empty => EdgeTransitions::Empty,
949            EdgeRow::Inline { symbol, target } => EdgeTransitions::Inline {
950                edge: Some((symbol, target)),
951            },
952            EdgeRow::Sparse { head, .. } => EdgeTransitions::Sparse {
953                table: self,
954                next: head,
955            },
956            EdgeRow::Dense { start, .. } => EdgeTransitions::Dense {
957                table: self,
958                start,
959                slot: 0,
960            },
961        }
962    }
963
964    fn slot(&self, symbol: i32) -> Option<u32> {
965        let slot = symbol
966            .checked_add(1)
967            .and_then(|value| u32::try_from(value).ok())?;
968        (slot < self.width).then_some(slot)
969    }
970
971    fn stats(&self) -> EdgeTableStats {
972        let mut stats = EdgeTableStats {
973            max_row_width: usize::try_from(self.width).expect("u32 row width fits in usize"),
974            dense_slots: self.dense_targets.len(),
975            sparse_entries: self.sparse_edges.len(),
976            ..EdgeTableStats::default()
977        };
978        for row in &self.rows {
979            let populated = match *row {
980                EdgeRow::Empty => {
981                    stats.empty_rows += 1;
982                    0
983                }
984                EdgeRow::Inline { .. } => {
985                    stats.sparse_rows += 1;
986                    1
987                }
988                EdgeRow::Dense { populated, .. } => {
989                    stats.dense_rows += 1;
990                    populated
991                }
992                EdgeRow::Sparse { len, .. } => {
993                    stats.sparse_rows += 1;
994                    len
995                }
996            };
997            stats.transitions = stats
998                .transitions
999                .saturating_add(usize::try_from(populated).expect("u32 edge count fits in usize"));
1000            stats.row_width_histogram[row_width_bucket(self.width)] += 1;
1001            stats.populated_edge_histogram[populated_edge_bucket(populated)] += 1;
1002            let bucket = density_bucket(populated, self.width);
1003            stats.edge_density_histogram[bucket] += 1;
1004        }
1005        stats
1006    }
1007
1008    const fn retained_bytes(&self) -> usize {
1009        self.rows.capacity() * size_of::<EdgeRow>()
1010            + self.dense_targets.capacity() * size_of::<DfaStateId>()
1011            + self.sparse_edges.capacity() * size_of::<SparseEdge>()
1012    }
1013
1014    fn clear(&mut self) {
1015        self.rows.clear();
1016        self.dense_targets.clear();
1017        self.sparse_edges.clear();
1018    }
1019}
1020
1021enum EdgeTransitions<'a> {
1022    Empty,
1023    Inline {
1024        edge: Option<(i32, DfaStateId)>,
1025    },
1026    Sparse {
1027        table: &'a EdgeTable,
1028        next: u32,
1029    },
1030    Dense {
1031        table: &'a EdgeTable,
1032        start: u32,
1033        slot: u32,
1034    },
1035}
1036
1037impl Iterator for EdgeTransitions<'_> {
1038    type Item = (i32, DfaStateId);
1039
1040    fn next(&mut self) -> Option<Self::Item> {
1041        match self {
1042            Self::Empty => None,
1043            Self::Inline { edge } => edge.take(),
1044            Self::Sparse { table, next } => {
1045                if *next == NO_EDGE_INDEX {
1046                    return None;
1047                }
1048                let edge = table.sparse_edges
1049                    [usize::try_from(*next).expect("u32 sparse edge index fits in usize")];
1050                *next = edge.next;
1051                Some((edge.symbol, edge.target))
1052            }
1053            Self::Dense { table, start, slot } => {
1054                while *slot < table.width {
1055                    let current = *slot;
1056                    *slot += 1;
1057                    let index =
1058                        usize::try_from(start.checked_add(current).expect("dense slot overflow"))
1059                            .expect("u32 dense slot fits in usize");
1060                    let target = table.dense_targets[index];
1061                    if target != NO_DFA_STATE {
1062                        let symbol =
1063                            i32::try_from(current).expect("bounded dense row slot fits in i32") - 1;
1064                        return Some((symbol, target));
1065                    }
1066                }
1067                None
1068            }
1069        }
1070    }
1071}
1072
1073#[derive(Debug, Default)]
1074struct EdgeTableStats {
1075    transitions: usize,
1076    max_row_width: usize,
1077    dense_rows: usize,
1078    sparse_rows: usize,
1079    empty_rows: usize,
1080    dense_slots: usize,
1081    sparse_entries: usize,
1082    row_width_histogram: [usize; 5],
1083    populated_edge_histogram: [usize; 6],
1084    edge_density_histogram: [usize; 6],
1085}
1086
1087const fn row_width_bucket(width: u32) -> usize {
1088    if width <= 64 {
1089        0
1090    } else if width <= 128 {
1091        1
1092    } else if width <= 256 {
1093        2
1094    } else if width <= 512 {
1095        3
1096    } else {
1097        4
1098    }
1099}
1100
1101const fn populated_edge_bucket(populated: u32) -> usize {
1102    match populated {
1103        0 => 0,
1104        1 => 1,
1105        2..=3 => 2,
1106        4..=7 => 3,
1107        8..=15 => 4,
1108        _ => 5,
1109    }
1110}
1111
1112const fn density_bucket(populated: u32, width: u32) -> usize {
1113    if populated == 0 || width == 0 {
1114        0
1115    } else if populated.saturating_mul(100) <= width {
1116        1
1117    } else if populated.saturating_mul(20) <= width {
1118        2
1119    } else if populated.saturating_mul(8) <= width {
1120        3
1121    } else if populated.saturating_mul(4) <= width {
1122        4
1123    } else {
1124        5
1125    }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130    use super::*;
1131    use crate::prediction::{AtnConfig, ContextArena, EMPTY_CONTEXT, PredictionWorkspace};
1132    use crate::token::TOKEN_EOF;
1133
1134    fn configs(state: usize, arena: &mut ContextArena) -> AtnConfigSet {
1135        let mut workspace = PredictionWorkspace::default();
1136        let mut configs = AtnConfigSet::new();
1137        configs.add(
1138            AtnConfig::new(state, 1, EMPTY_CONTEXT, arena),
1139            arena,
1140            &mut workspace,
1141        );
1142        configs
1143    }
1144
1145    #[test]
1146    fn dfa_reuses_equal_config_sets_without_key_clone() {
1147        let mut arena = ContextArena::new();
1148        let configs = configs(1, &mut arena);
1149        let mut dfa = ParserDfa::with_max_token_type(0, 0, 16);
1150
1151        assert_eq!(
1152            dfa.add_state(DfaStateBuilder::new(configs.clone())).index(),
1153            0
1154        );
1155        assert_eq!(dfa.add_state(DfaStateBuilder::new(configs)).index(), 0);
1156        assert_eq!(dfa.state_count(), 1);
1157        assert_eq!(dfa.stats().states_deduplicated, 1);
1158    }
1159
1160    #[test]
1161    fn fingerprint_collisions_are_verified_structurally() {
1162        let mut arena = ContextArena::new();
1163        let mut dfa = ParserDfa::with_max_token_type(0, 0, 16);
1164        let first = DfaStateBuilder::new(configs(1, &mut arena));
1165        let second = DfaStateBuilder::new(configs(2, &mut arena));
1166        let fingerprint = 7;
1167
1168        let first_id = dfa.insert_state_with_fingerprint(first, fingerprint);
1169        let second_id = dfa.insert_state_with_fingerprint(second, fingerprint);
1170        let first_configs = configs(1, &mut arena);
1171
1172        assert_eq!(dfa.find_state(fingerprint, &first_configs), Some(first_id));
1173        assert_ne!(first_id, second_id);
1174        assert_eq!(dfa.stats().fingerprint_collisions, 1);
1175    }
1176
1177    #[test]
1178    fn sparse_edges_are_sorted_and_use_scalar_targets() {
1179        let mut arena = ContextArena::new();
1180        let mut dfa = ParserDfa::with_max_token_type(0, 0, 32);
1181        let source = dfa.add_state(DfaStateBuilder::new(configs(1, &mut arena)));
1182        let target = dfa.add_state(DfaStateBuilder::new(configs(2, &mut arena)));
1183
1184        dfa.add_edge(source, 5, target);
1185        dfa.add_edge(source, -1, target);
1186        dfa.add_edge(source, 2, target);
1187
1188        assert_eq!(dfa.edge(source, -1), Some(target));
1189        assert_eq!(dfa.edge(source, 4), None);
1190        assert_eq!(
1191            dfa.state(source)
1192                .expect("source")
1193                .transitions()
1194                .map(|edge| edge.symbol)
1195                .collect::<Vec<_>>(),
1196            [-1, 2, 5]
1197        );
1198    }
1199
1200    #[test]
1201    fn populated_rows_promote_into_shared_dense_slab() {
1202        let mut arena = ContextArena::new();
1203        let mut dfa = ParserDfa::with_max_token_type(0, 0, 62);
1204        let source = dfa.add_state(DfaStateBuilder::new(configs(1, &mut arena)));
1205        let target = dfa.add_state(DfaStateBuilder::new(configs(2, &mut arena)));
1206
1207        for symbol in 0..8 {
1208            dfa.add_edge(source, symbol, target);
1209        }
1210
1211        assert_eq!(dfa.stats().dense_rows, 1);
1212        assert_eq!(dfa.edge(source, 7), Some(target));
1213        assert_eq!(dfa.edge(source, 8), None);
1214    }
1215
1216    #[test]
1217    fn creating_states_does_not_allocate_edge_rows() {
1218        let mut arena = ContextArena::new();
1219        let mut dfa = ParserDfa::with_max_token_type(0, 0, 255);
1220
1221        for state in 0..32 {
1222            dfa.add_state(DfaStateBuilder::new(configs(state, &mut arena)));
1223        }
1224
1225        let stats = dfa.stats();
1226        assert_eq!(stats.dense_slots, 0);
1227        assert_eq!(stats.sparse_entries, 0);
1228        assert_eq!(stats.empty_rows, 32);
1229    }
1230
1231    #[test]
1232    fn edge_updates_do_not_duplicate_sparse_entries() {
1233        let mut arena = ContextArena::new();
1234        let mut dfa = ParserDfa::with_max_token_type(0, 0, 32);
1235        let source = dfa.add_state(DfaStateBuilder::new(configs(1, &mut arena)));
1236        let first = dfa.add_state(DfaStateBuilder::new(configs(2, &mut arena)));
1237        let second = dfa.add_state(DfaStateBuilder::new(configs(3, &mut arena)));
1238
1239        dfa.add_edge(source, TOKEN_EOF, first);
1240        dfa.add_edge(source, TOKEN_EOF, first);
1241        dfa.add_edge(source, TOKEN_EOF, second);
1242
1243        assert_eq!(dfa.edge(source, TOKEN_EOF), Some(second));
1244        assert_eq!(dfa.state(source).expect("source").transitions().count(), 1);
1245    }
1246
1247    #[test]
1248    fn out_of_vocabulary_edges_are_not_stored() {
1249        let mut arena = ContextArena::new();
1250        let mut dfa = ParserDfa::with_max_token_type(0, 0, 4);
1251        let source = dfa.add_state(DfaStateBuilder::new(configs(1, &mut arena)));
1252        let target = dfa.add_state(DfaStateBuilder::new(configs(2, &mut arena)));
1253
1254        dfa.add_edge(source, -2, target);
1255        dfa.add_edge(source, 5, target);
1256
1257        assert_eq!(dfa.edge(source, -2), None);
1258        assert_eq!(dfa.edge(source, 5), None);
1259        assert_eq!(dfa.stats().transitions, 0);
1260    }
1261
1262    #[test]
1263    fn precedence_dfa_tracks_start_states_by_compact_id() {
1264        let mut dfa = ParserDfa::new(10, 2);
1265        dfa.set_precedence_dfa(true);
1266        let start = dfa.start_state().expect("precedence root");
1267        dfa.set_precedence_start_state(4, start);
1268
1269        assert!(dfa.is_precedence_dfa());
1270        assert_eq!(dfa.precedence_start_state(4), Some(start));
1271        assert_eq!(dfa.precedence_start_state(3), None);
1272    }
1273}