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