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
14const 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#[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 pub fn index(self) -> usize {
45 usize::try_from(self.0).expect("u32 DFA state ID fits in usize")
46 }
47}
48
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
51pub struct DfaTransition {
52 pub source: DfaStateId,
53 pub symbol: i32,
54 pub target: DfaStateId,
55}
56
57#[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 pub row_width_histogram: [usize; 5],
70 pub populated_edge_histogram: [usize; 6],
72 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#[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#[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}