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