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