Skip to main content

antlr4_runtime/
dfa.rs

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