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