Skip to main content

antlr4_runtime/
dfa.rs

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