1use std::cmp::Ordering;
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5use std::hash::{BuildHasherDefault, Hash, Hasher};
6use std::mem::size_of;
7
8pub const EMPTY_RETURN_STATE: usize = usize::MAX;
9const COMPACT_EMPTY_RETURN_STATE: u32 = u32::MAX;
10
11#[derive(Debug, Default)]
13pub struct PredictionFxHasher {
14 hash: u64,
15}
16
17const FX_ROT: u32 = 5;
18const FX_SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
19
20impl Hasher for PredictionFxHasher {
21 #[inline]
22 fn write(&mut self, bytes: &[u8]) {
23 let mut bytes = bytes;
24 while bytes.len() >= 8 {
25 let (head, rest) = bytes.split_at(8);
26 let word = u64::from_le_bytes(head.try_into().expect("8-byte chunk"));
27 self.hash = (self.hash.rotate_left(FX_ROT) ^ word).wrapping_mul(FX_SEED);
28 bytes = rest;
29 }
30 for &byte in bytes {
31 self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(byte)).wrapping_mul(FX_SEED);
32 }
33 }
34
35 #[inline]
36 fn write_u8(&mut self, value: u8) {
37 self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(value)).wrapping_mul(FX_SEED);
38 }
39
40 #[inline]
41 fn write_u32(&mut self, value: u32) {
42 self.hash = (self.hash.rotate_left(FX_ROT) ^ u64::from(value)).wrapping_mul(FX_SEED);
43 }
44
45 #[inline]
46 fn write_u64(&mut self, value: u64) {
47 self.hash = (self.hash.rotate_left(FX_ROT) ^ value).wrapping_mul(FX_SEED);
48 }
49
50 #[inline]
51 fn write_usize(&mut self, value: usize) {
52 self.hash = (self.hash.rotate_left(FX_ROT) ^ value as u64).wrapping_mul(FX_SEED);
53 }
54
55 #[inline]
56 fn write_i32(&mut self, value: i32) {
57 self.write_u32(i32::cast_unsigned(value));
58 }
59
60 #[inline]
61 fn finish(&self) -> u64 {
62 self.hash
63 }
64}
65
66type FxHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PredictionFxHasher>>;
67
68#[repr(transparent)]
70#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub struct ContextId(u32);
72
73pub const EMPTY_CONTEXT: ContextId = ContextId(0);
74
75impl ContextId {
76 pub(crate) const fn compact(self) -> u32 {
77 self.0
78 }
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82enum ContextTag {
83 Empty,
84 Singleton,
85 Array,
86}
87
88#[derive(Clone, Copy, Debug)]
89struct ContextRecord {
90 tag: ContextTag,
91 cached_hash: u64,
92 parent_or_start: u32,
93 return_state_or_len: u32,
94}
95
96#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
98pub struct PredictionContextStats {
99 pub contexts_created: usize,
100 pub singleton_contexts: usize,
101 pub array_contexts: usize,
102 pub array_entries: usize,
103 pub interner_hits: usize,
104 pub pooled_bytes: usize,
105 pub retained_bytes: usize,
108 pub context_capacity: usize,
109 pub array_parent_capacity: usize,
110 pub array_return_state_capacity: usize,
111 pub interner_capacity: usize,
112 pub workspace_merge_cache_entries: usize,
113 pub workspace_merge_cache_capacity: usize,
114 pub workspace_entry_capacity: usize,
115 pub outer_context_cache_hits: usize,
116 pub outer_context_cache_misses: usize,
117}
118
119#[derive(Debug)]
121pub(crate) struct ContextArena {
122 records: Vec<ContextRecord>,
123 array_parents: Vec<ContextId>,
124 array_return_states: Vec<u32>,
125 interner_heads: FxHashMap<u64, ContextId>,
126 interner_next: Vec<Option<ContextId>>,
127 interner_hits: usize,
128 #[cfg(debug_assertions)]
129 generation: u64,
130}
131
132#[cfg(debug_assertions)]
133fn next_context_arena_generation() -> u64 {
134 use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
135
136 static NEXT_GENERATION: AtomicU64 = AtomicU64::new(1);
137 NEXT_GENERATION.fetch_add(1, AtomicOrdering::Relaxed)
138}
139
140impl ContextArena {
141 pub(crate) fn new() -> Self {
142 let empty = ContextRecord {
143 tag: ContextTag::Empty,
144 cached_hash: prediction_context_empty_hash(),
145 parent_or_start: 0,
146 return_state_or_len: 0,
147 };
148 let mut interner_heads = FxHashMap::default();
149 interner_heads.insert(empty.cached_hash, EMPTY_CONTEXT);
150 Self {
151 records: vec![empty],
152 array_parents: Vec::new(),
153 array_return_states: Vec::new(),
154 interner_heads,
155 interner_next: vec![None],
156 interner_hits: 0,
157 #[cfg(debug_assertions)]
158 generation: next_context_arena_generation(),
159 }
160 }
161
162 #[cfg(debug_assertions)]
163 pub(crate) const fn generation(&self) -> u64 {
164 self.generation
165 }
166
167 pub(crate) fn stats(&self) -> PredictionContextStats {
168 let mut singleton_contexts = 0;
169 let mut array_contexts = 0;
170 for record in &self.records {
171 match record.tag {
172 ContextTag::Empty => {}
173 ContextTag::Singleton => singleton_contexts += 1,
174 ContextTag::Array => array_contexts += 1,
175 }
176 }
177 PredictionContextStats {
178 contexts_created: self.records.len(),
179 singleton_contexts,
180 array_contexts,
181 array_entries: self.array_parents.len(),
182 interner_hits: self.interner_hits,
183 pooled_bytes: self.records.len() * size_of::<ContextRecord>()
184 + self.array_parents.len() * size_of::<ContextId>()
185 + self.array_return_states.len() * size_of::<u32>()
186 + self.interner_next.len() * size_of::<Option<ContextId>>(),
187 retained_bytes: self.records.capacity() * size_of::<ContextRecord>()
188 + self.array_parents.capacity() * size_of::<ContextId>()
189 + self.array_return_states.capacity() * size_of::<u32>()
190 + self.interner_heads.capacity() * size_of::<(u64, ContextId)>()
191 + self.interner_next.capacity() * size_of::<Option<ContextId>>(),
192 context_capacity: self.records.capacity(),
193 array_parent_capacity: self.array_parents.capacity(),
194 array_return_state_capacity: self.array_return_states.capacity(),
195 interner_capacity: self.interner_heads.capacity(),
196 workspace_merge_cache_entries: 0,
197 workspace_merge_cache_capacity: 0,
198 workspace_entry_capacity: 0,
199 outer_context_cache_hits: 0,
200 outer_context_cache_misses: 0,
201 }
202 }
203
204 pub(crate) fn singleton(&mut self, parent: ContextId, return_state: usize) -> ContextId {
205 self.assert_valid(parent);
206 if return_state == EMPTY_RETURN_STATE {
207 return EMPTY_CONTEXT;
208 }
209 #[cfg(feature = "perf-counters")]
210 crate::perf::record_context_cache_call();
211 let return_state =
212 u32::try_from(return_state).expect("prediction return state must fit in u32");
213 let cached_hash = prediction_context_singleton_hash(self.cached_hash(parent), return_state);
214 if let Some(existing) = self.find_interned(cached_hash, |record| {
215 record.tag == ContextTag::Singleton
216 && record.parent_or_start == parent.0
217 && record.return_state_or_len == return_state
218 }) {
219 self.interner_hits = self.interner_hits.saturating_add(1);
220 #[cfg(feature = "perf-counters")]
221 crate::perf::record_context_cache_hit();
222 return existing;
223 }
224 #[cfg(feature = "perf-counters")]
225 {
226 crate::perf::record_context_cache_miss();
227 crate::perf::record_context_cache_insert();
228 }
229 self.push_record(ContextRecord {
230 tag: ContextTag::Singleton,
231 cached_hash,
232 parent_or_start: parent.0,
233 return_state_or_len: return_state,
234 })
235 }
236
237 fn intern_entries(&mut self, entries: &[(ContextId, u32)]) -> ContextId {
238 match entries {
239 [] => EMPTY_CONTEXT,
240 [(parent, return_state)] => {
241 if *return_state == COMPACT_EMPTY_RETURN_STATE {
242 EMPTY_CONTEXT
243 } else {
244 self.singleton(
245 *parent,
246 usize::try_from(*return_state).expect("u32 return state fits in usize"),
247 )
248 }
249 }
250 _ => {
251 debug_assert!(
252 entries
253 .windows(2)
254 .all(|pair| { compare_entries(pair[0], pair[1]) == Ordering::Less })
255 );
256 #[cfg(feature = "perf-counters")]
257 crate::perf::record_context_cache_call();
258 let cached_hash = prediction_context_array_hash(self, entries);
259 if let Some(existing) = self.find_interned(cached_hash, |record| {
260 if record.tag != ContextTag::Array
261 || usize::try_from(record.return_state_or_len).ok() != Some(entries.len())
262 {
263 return false;
264 }
265 let start =
266 usize::try_from(record.parent_or_start).expect("u32 pool index fits usize");
267 let end = start + entries.len();
268 self.array_parents[start..end]
269 .iter()
270 .copied()
271 .zip(self.array_return_states[start..end].iter().copied())
272 .eq(entries.iter().copied())
273 }) {
274 self.interner_hits = self.interner_hits.saturating_add(1);
275 #[cfg(feature = "perf-counters")]
276 crate::perf::record_context_cache_hit();
277 return existing;
278 }
279 #[cfg(feature = "perf-counters")]
280 {
281 crate::perf::record_context_cache_miss();
282 crate::perf::record_context_cache_insert();
283 }
284 let start = u32::try_from(self.array_parents.len())
285 .expect("prediction-context parent pool must fit in u32");
286 let len = u32::try_from(entries.len())
287 .expect("prediction-context array length must fit in u32");
288 self.array_parents
289 .extend(entries.iter().map(|(parent, _)| *parent));
290 self.array_return_states
291 .extend(entries.iter().map(|(_, return_state)| *return_state));
292 self.push_record(ContextRecord {
293 tag: ContextTag::Array,
294 cached_hash,
295 parent_or_start: start,
296 return_state_or_len: len,
297 })
298 }
299 }
300 }
301
302 fn find_interned(
303 &self,
304 cached_hash: u64,
305 matches: impl Fn(&ContextRecord) -> bool,
306 ) -> Option<ContextId> {
307 let mut candidate = self.interner_heads.get(&cached_hash).copied();
308 while let Some(id) = candidate {
309 let index = usize::try_from(id.0).expect("u32 context ID fits in usize");
310 let record = &self.records[index];
311 if matches(record) {
312 return Some(id);
313 }
314 candidate = self.interner_next[index];
315 }
316 None
317 }
318
319 fn push_record(&mut self, record: ContextRecord) -> ContextId {
320 let id = ContextId(
321 u32::try_from(self.records.len()).expect("prediction-context arena must fit in u32"),
322 );
323 let previous = self.interner_heads.insert(record.cached_hash, id);
324 self.records.push(record);
325 self.interner_next.push(previous);
326 id
327 }
328
329 pub(crate) fn merge(
330 &mut self,
331 left: ContextId,
332 right: ContextId,
333 root_is_wildcard: bool,
334 workspace: &mut PredictionWorkspace,
335 ) -> ContextId {
336 self.assert_valid(left);
337 self.assert_valid(right);
338 #[cfg(feature = "perf-counters")]
339 crate::perf::record_context_merge_call();
340 if left == right {
341 #[cfg(feature = "perf-counters")]
342 crate::perf::record_context_merge_identical();
343 return left;
344 }
345 let key = MergeKey::new(left, right, root_is_wildcard);
346 if let Some(merged) = workspace.merge_cache.get(&key).copied() {
347 #[cfg(feature = "perf-counters")]
348 crate::perf::record_context_merge_cache_hit();
349 return merged;
350 }
351 #[cfg(feature = "perf-counters")]
352 {
353 crate::perf::record_context_merge_cache_miss();
354 crate::perf::record_context_merge_uncached();
355 }
356 let merged = if root_is_wildcard && (left == EMPTY_CONTEXT || right == EMPTY_CONTEXT) {
357 EMPTY_CONTEXT
358 } else {
359 self.merge_uncached(left, right, root_is_wildcard, workspace)
360 };
361 workspace.merge_cache.insert(key, merged);
362 merged
363 }
364
365 fn merge_uncached(
366 &mut self,
367 left: ContextId,
368 right: ContextId,
369 root_is_wildcard: bool,
370 workspace: &mut PredictionWorkspace,
371 ) -> ContextId {
372 match (self.tag(left), self.tag(right)) {
373 (ContextTag::Array, ContextTag::Array) => {
374 self.merge_arrays(left, right, root_is_wildcard, workspace)
375 }
376 (ContextTag::Array, _) => {
377 let entry = self.first_entry(right);
378 self.merge_array_with_entry(left, entry, root_is_wildcard, workspace)
379 }
380 (_, ContextTag::Array) => {
381 let entry = self.first_entry(left);
382 self.merge_array_with_entry(right, entry, root_is_wildcard, workspace)
383 }
384 _ => self.merge_two_entries(
385 self.first_entry(left),
386 self.first_entry(right),
387 root_is_wildcard,
388 workspace,
389 ),
390 }
391 }
392
393 fn merge_two_entries(
394 &mut self,
395 left: (ContextId, u32),
396 right: (ContextId, u32),
397 root_is_wildcard: bool,
398 workspace: &mut PredictionWorkspace,
399 ) -> ContextId {
400 if left.1 == right.1 {
401 let parent = if left.0 == right.0 {
402 left.0
403 } else {
404 self.merge(left.0, right.0, root_is_wildcard, workspace)
405 };
406 return self.intern_entries(&[(parent, left.1)]);
407 }
408
409 let start = workspace.entries.len();
410 if right.1 < left.1 {
411 workspace.entries.extend([right, left]);
412 } else {
413 workspace.entries.extend([left, right]);
414 }
415 self.intern_workspace_entries(workspace, start)
416 }
417
418 fn intern_workspace_entries(
419 &mut self,
420 workspace: &mut PredictionWorkspace,
421 start: usize,
422 ) -> ContextId {
423 let context = self.intern_entries(&workspace.entries[start..]);
424 workspace.entries.truncate(start);
425 context
426 }
427
428 fn merge_array_with_entry(
429 &mut self,
430 array: ContextId,
431 entry: (ContextId, u32),
432 root_is_wildcard: bool,
433 workspace: &mut PredictionWorkspace,
434 ) -> ContextId {
435 let array_len = self.len(array);
436 let mut insert_index = array_len;
437 for index in 0..array_len {
438 let current = self.entry(array, index).expect("array entry in range");
439 match entry.1.cmp(¤t.1) {
440 Ordering::Less => {
441 insert_index = index;
442 break;
443 }
444 Ordering::Equal => {
445 let parent = if entry.0 == current.0 {
446 current.0
447 } else {
448 self.merge(entry.0, current.0, root_is_wildcard, workspace)
449 };
450 if parent == current.0 {
451 return array;
452 }
453
454 let start = workspace.entries.len();
455 for entry_index in 0..array_len {
456 let array_entry = self
457 .entry(array, entry_index)
458 .expect("array entry in range");
459 workspace.entries.push(if entry_index == index {
460 (parent, current.1)
461 } else {
462 array_entry
463 });
464 }
465 return self.intern_workspace_entries(workspace, start);
466 }
467 Ordering::Greater => {}
468 }
469 }
470
471 let start = workspace.entries.len();
472 for index in 0..insert_index {
473 workspace
474 .entries
475 .push(self.entry(array, index).expect("array entry in range"));
476 }
477 workspace.entries.push(entry);
478 for index in insert_index..array_len {
479 workspace
480 .entries
481 .push(self.entry(array, index).expect("array entry in range"));
482 }
483 self.intern_workspace_entries(workspace, start)
484 }
485
486 fn merge_arrays(
487 &mut self,
488 left: ContextId,
489 right: ContextId,
490 root_is_wildcard: bool,
491 workspace: &mut PredictionWorkspace,
492 ) -> ContextId {
493 let start = workspace.entries.len();
494 let left_len = self.len(left);
495 let right_len = self.len(right);
496 let mut left_index = 0;
497 let mut right_index = 0;
498 while left_index < left_len && right_index < right_len {
499 let left_entry = self.entry(left, left_index).expect("array entry in range");
500 let right_entry = self
501 .entry(right, right_index)
502 .expect("array entry in range");
503 match left_entry.1.cmp(&right_entry.1) {
504 Ordering::Less => {
505 workspace.entries.push(left_entry);
506 left_index += 1;
507 }
508 Ordering::Greater => {
509 workspace.entries.push(right_entry);
510 right_index += 1;
511 }
512 Ordering::Equal => {
513 let parent = if left_entry.0 == right_entry.0 {
514 left_entry.0
515 } else {
516 self.merge(left_entry.0, right_entry.0, root_is_wildcard, workspace)
517 };
518 workspace.entries.push((parent, left_entry.1));
519 left_index += 1;
520 right_index += 1;
521 }
522 }
523 }
524 while left_index < left_len {
525 workspace
526 .entries
527 .push(self.entry(left, left_index).expect("array entry in range"));
528 left_index += 1;
529 }
530 while right_index < right_len {
531 workspace.entries.push(
532 self.entry(right, right_index)
533 .expect("array entry in range"),
534 );
535 right_index += 1;
536 }
537 self.intern_workspace_entries(workspace, start)
538 }
539
540 pub(crate) fn len(&self, context: ContextId) -> usize {
541 let record = self.record(context);
542 match record.tag {
543 ContextTag::Empty | ContextTag::Singleton => 1,
544 ContextTag::Array => usize::try_from(record.return_state_or_len)
545 .expect("u32 context length fits in usize"),
546 }
547 }
548
549 pub(crate) fn is_empty(&self, context: ContextId) -> bool {
550 self.assert_valid(context);
551 context == EMPTY_CONTEXT
552 }
553
554 pub(crate) fn has_empty_path(&self, context: ContextId) -> bool {
555 if context == EMPTY_CONTEXT {
556 return true;
557 }
558 let record = self.record(context);
559 match record.tag {
560 ContextTag::Empty => true,
561 ContextTag::Singleton => false,
562 ContextTag::Array => {
563 let len = usize::try_from(record.return_state_or_len)
564 .expect("u32 context length fits in usize");
565 let start = usize::try_from(record.parent_or_start)
566 .expect("u32 context pool index fits in usize");
567 self.array_return_states[start + len - 1] == COMPACT_EMPTY_RETURN_STATE
568 }
569 }
570 }
571
572 pub(crate) fn return_state(&self, context: ContextId, index: usize) -> Option<usize> {
573 let (_, return_state) = self.entry(context, index)?;
574 Some(expand_return_state(return_state))
575 }
576
577 pub(crate) fn parent(&self, context: ContextId, index: usize) -> Option<ContextId> {
578 if context == EMPTY_CONTEXT {
579 self.assert_valid(context);
580 return None;
581 }
582 self.entry(context, index).map(|(parent, _)| parent)
583 }
584
585 fn first_entry(&self, context: ContextId) -> (ContextId, u32) {
586 self.entry(context, 0)
587 .expect("empty and singleton contexts have one logical entry")
588 }
589
590 fn entry(&self, context: ContextId, index: usize) -> Option<(ContextId, u32)> {
591 let record = self.record(context);
592 match record.tag {
593 ContextTag::Empty if index == 0 => Some((EMPTY_CONTEXT, COMPACT_EMPTY_RETURN_STATE)),
594 ContextTag::Singleton if index == 0 => Some((
595 ContextId(record.parent_or_start),
596 record.return_state_or_len,
597 )),
598 ContextTag::Array => {
599 let len = usize::try_from(record.return_state_or_len).ok()?;
600 if index >= len {
601 return None;
602 }
603 let start = usize::try_from(record.parent_or_start).ok()?;
604 Some((
605 self.array_parents[start + index],
606 self.array_return_states[start + index],
607 ))
608 }
609 ContextTag::Empty | ContextTag::Singleton => None,
610 }
611 }
612
613 fn tag(&self, context: ContextId) -> ContextTag {
614 self.record(context).tag
615 }
616
617 fn cached_hash(&self, context: ContextId) -> u64 {
618 self.record(context).cached_hash
619 }
620
621 fn record(&self, context: ContextId) -> &ContextRecord {
622 self.assert_valid(context);
623 &self.records[usize::try_from(context.0).expect("u32 context ID fits in usize")]
624 }
625
626 pub(crate) fn assert_valid(&self, context: ContextId) {
627 assert!(
628 usize::try_from(context.0).is_ok_and(|index| index < self.records.len()),
629 "prediction ContextId does not belong to this store"
630 );
631 }
632
633 pub(crate) fn import_all(
634 &mut self,
635 source: &Self,
636 workspace: &mut PredictionWorkspace,
637 ) -> Vec<ContextId> {
638 workspace.entries.clear();
639 let mut remap = Vec::with_capacity(source.records.len());
640 remap.push(EMPTY_CONTEXT);
641 for source_index in 1..source.records.len() {
642 let source_id = ContextId(
643 u32::try_from(source_index).expect("source prediction-context ID fits in u32"),
644 );
645 let imported = match source.tag(source_id) {
646 ContextTag::Empty => EMPTY_CONTEXT,
647 ContextTag::Singleton => {
648 let (parent, return_state) = source.first_entry(source_id);
649 let parent_index =
650 usize::try_from(parent.0).expect("u32 context ID fits usize");
651 assert!(
652 parent_index < remap.len(),
653 "prediction contexts must reference earlier arena records"
654 );
655 self.singleton(remap[parent_index], expand_return_state(return_state))
656 }
657 ContextTag::Array => {
658 let start = workspace.entries.len();
659 for entry_index in 0..source.len(source_id) {
660 let (parent, return_state) = source
661 .entry(source_id, entry_index)
662 .expect("source array entry in range");
663 let parent_index =
664 usize::try_from(parent.0).expect("u32 context ID fits usize");
665 assert!(
666 parent_index < remap.len(),
667 "prediction contexts must reference earlier arena records"
668 );
669 workspace.entries.push((remap[parent_index], return_state));
670 }
671 workspace
672 .entries
673 .sort_unstable_by(|left, right| compare_entries(*left, *right));
674 workspace.entries.dedup();
675 self.intern_workspace_entries(workspace, start)
676 }
677 };
678 remap.push(imported);
679 }
680 remap
681 }
682}
683
684impl Default for ContextArena {
685 fn default() -> Self {
686 Self::new()
687 }
688}
689
690fn compare_entries(left: (ContextId, u32), right: (ContextId, u32)) -> Ordering {
691 left.1.cmp(&right.1)
692}
693
694fn expand_return_state(return_state: u32) -> usize {
695 if return_state == COMPACT_EMPTY_RETURN_STATE {
696 EMPTY_RETURN_STATE
697 } else {
698 usize::try_from(return_state).expect("u32 return state fits in usize")
699 }
700}
701
702fn prediction_context_empty_hash() -> u64 {
703 let mut hasher = PredictionFxHasher::default();
704 hasher.write_u8(0);
705 hasher.finish()
706}
707
708fn prediction_context_singleton_hash(parent_hash: u64, return_state: u32) -> u64 {
709 let mut hasher = PredictionFxHasher::default();
710 hasher.write_u8(1);
711 hasher.write_u64(parent_hash);
712 hasher.write_u32(return_state);
713 hasher.finish()
714}
715
716fn prediction_context_array_hash(arena: &ContextArena, entries: &[(ContextId, u32)]) -> u64 {
717 let mut hasher = PredictionFxHasher::default();
718 hasher.write_u8(2);
719 hasher.write_usize(entries.len());
720 for (parent, _) in entries {
721 hasher.write_u64(arena.cached_hash(*parent));
722 }
723 hasher.write_usize(entries.len());
724 for (_, return_state) in entries {
725 hasher.write_u32(*return_state);
726 }
727 hasher.finish()
728}
729
730#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
731struct MergeKey {
732 left: ContextId,
733 right: ContextId,
734 root_is_wildcard: bool,
735}
736
737impl MergeKey {
738 fn new(left: ContextId, right: ContextId, root_is_wildcard: bool) -> Self {
739 let (left, right) = if right < left {
740 (right, left)
741 } else {
742 (left, right)
743 };
744 Self {
745 left,
746 right,
747 root_is_wildcard,
748 }
749 }
750}
751
752const MAX_RETAINED_MERGE_CACHE_ENTRIES: usize = 65_536;
753const MAX_RETAINED_CONTEXT_ENTRIES: usize = 16_384;
754
755#[derive(Debug, Default)]
757pub(crate) struct PredictionWorkspace {
758 merge_cache: FxHashMap<MergeKey, ContextId>,
759 entries: Vec<(ContextId, u32)>,
760}
761
762impl PredictionWorkspace {
763 pub(crate) fn reset(&mut self) {
764 if self.merge_cache.capacity() > MAX_RETAINED_MERGE_CACHE_ENTRIES {
765 self.merge_cache = FxHashMap::default();
766 } else {
767 self.merge_cache.clear();
768 }
769 if self.entries.capacity() > MAX_RETAINED_CONTEXT_ENTRIES {
770 self.entries = Vec::new();
771 } else {
772 self.entries.clear();
773 }
774 }
775
776 pub(crate) fn merge_cache_capacity(&self) -> usize {
777 self.merge_cache.capacity()
778 }
779
780 pub(crate) fn merge_cache_len(&self) -> usize {
781 self.merge_cache.len()
782 }
783
784 pub(crate) const fn entry_capacity(&self) -> usize {
785 self.entries.capacity()
786 }
787
788 pub(crate) fn retained_bytes(&self) -> usize {
789 self.merge_cache.capacity() * size_of::<(MergeKey, ContextId)>()
790 + self.entries.capacity() * size_of::<(ContextId, u32)>()
791 }
792}
793
794#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
795pub enum SemanticContext {
796 None,
797 Predicate {
798 rule_index: usize,
799 pred_index: usize,
800 context_dependent: bool,
801 },
802 Precedence {
803 precedence: i32,
804 },
805 And(Vec<Self>),
806 Or(Vec<Self>),
807}
808
809impl SemanticContext {
810 pub const fn none() -> Self {
811 Self::None
812 }
813
814 pub fn and(left: Self, right: Self) -> Self {
815 combine_semantic_context(left, right, true)
816 }
817
818 pub fn or(left: Self, right: Self) -> Self {
819 combine_semantic_context(left, right, false)
820 }
821
822 pub const fn is_none(&self) -> bool {
823 matches!(self, Self::None)
824 }
825}
826
827#[repr(transparent)]
828#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
829pub(crate) struct SemanticContextId(u32);
830
831#[derive(Debug)]
832pub(crate) struct SemanticContextArena {
833 records: Vec<SemanticContext>,
834 interner_heads: FxHashMap<u64, SemanticContextId>,
835 interner_next: Vec<Option<SemanticContextId>>,
836}
837
838impl SemanticContextArena {
839 pub(crate) fn new() -> Self {
840 let semantic_context = SemanticContext::None;
841 let cached_hash = semantic_context_hash(&semantic_context);
842 let mut interner_heads = FxHashMap::default();
843 interner_heads.insert(cached_hash, SemanticContextId::default());
844 Self {
845 records: vec![semantic_context],
846 interner_heads,
847 interner_next: vec![None],
848 }
849 }
850
851 pub(crate) fn intern(&mut self, semantic_context: SemanticContext) -> SemanticContextId {
852 if semantic_context.is_none() {
853 return SemanticContextId::default();
854 }
855 let cached_hash = semantic_context_hash(&semantic_context);
856 if let Some(id) = self.find_interned(cached_hash, &semantic_context) {
857 return id;
858 }
859 let id = SemanticContextId(
860 u32::try_from(self.records.len()).expect("semantic-context arena exhausted"),
861 );
862 let previous = self.interner_heads.insert(cached_hash, id);
863 self.records.push(semantic_context);
864 self.interner_next.push(previous);
865 id
866 }
867
868 pub(crate) fn and(
869 &mut self,
870 left: SemanticContextId,
871 right: SemanticContext,
872 ) -> SemanticContextId {
873 let combined = SemanticContext::and(self.get(left).clone(), right);
874 self.intern(combined)
875 }
876
877 pub(crate) fn get(&self, id: SemanticContextId) -> &SemanticContext {
878 self.assert_valid(id);
879 &self.records[usize::try_from(id.0).expect("u32 semantic-context ID fits usize")]
880 }
881
882 pub(crate) fn import_all(&mut self, source: &Self) -> Vec<SemanticContextId> {
883 source
884 .records
885 .iter()
886 .cloned()
887 .map(|semantic_context| self.intern(semantic_context))
888 .collect()
889 }
890
891 pub(crate) const fn len(&self) -> usize {
892 self.records.len()
893 }
894
895 pub(crate) fn retained_bytes(&self) -> usize {
896 self.records.capacity() * size_of::<SemanticContext>()
897 + self
898 .records
899 .iter()
900 .map(semantic_context_heap_retained_bytes)
901 .sum::<usize>()
902 + self.interner_heads.capacity() * size_of::<(u64, SemanticContextId)>()
903 + self.interner_next.capacity() * size_of::<Option<SemanticContextId>>()
904 }
905
906 fn find_interned(
907 &self,
908 cached_hash: u64,
909 semantic_context: &SemanticContext,
910 ) -> Option<SemanticContextId> {
911 let mut candidate = self.interner_heads.get(&cached_hash).copied();
912 while let Some(id) = candidate {
913 let index = usize::try_from(id.0).ok()?;
914 if self.records.get(index) == Some(semantic_context) {
915 return Some(id);
916 }
917 candidate = self.interner_next.get(index).copied().flatten();
918 }
919 None
920 }
921
922 fn assert_valid(&self, id: SemanticContextId) {
923 assert!(
924 usize::try_from(id.0).is_ok_and(|index| index < self.records.len()),
925 "semantic-context ID does not belong to this store"
926 );
927 }
928}
929
930impl Default for SemanticContextArena {
931 fn default() -> Self {
932 Self::new()
933 }
934}
935
936fn semantic_context_hash(semantic_context: &SemanticContext) -> u64 {
937 let mut hasher = PredictionFxHasher::default();
938 semantic_context.hash(&mut hasher);
939 hasher.finish()
940}
941
942fn semantic_context_heap_retained_bytes(semantic_context: &SemanticContext) -> usize {
943 match semantic_context {
944 SemanticContext::And(children) | SemanticContext::Or(children) => {
945 children.capacity() * size_of::<SemanticContext>()
946 + children
947 .iter()
948 .map(semantic_context_heap_retained_bytes)
949 .sum::<usize>()
950 }
951 SemanticContext::None
952 | SemanticContext::Predicate { .. }
953 | SemanticContext::Precedence { .. } => 0,
954 }
955}
956
957fn combine_semantic_context(
958 left: SemanticContext,
959 right: SemanticContext,
960 and: bool,
961) -> SemanticContext {
962 if left == right {
963 return left;
964 }
965 if left.is_none() {
966 return right;
967 }
968 if right.is_none() {
969 return left;
970 }
971 let mut entries = Vec::new();
972 for context in [left, right] {
973 match (and, context) {
974 (true, SemanticContext::And(children)) | (false, SemanticContext::Or(children)) => {
975 entries.extend(children);
976 }
977 (_, other) => entries.push(other),
978 }
979 }
980 entries.sort();
981 entries.dedup();
982 if and {
983 SemanticContext::And(entries)
984 } else {
985 SemanticContext::Or(entries)
986 }
987}
988
989#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
990pub(crate) struct PredictionRuleCall {
991 pub(crate) source_state: usize,
992 pub(crate) rule_index: usize,
993}
994
995#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
996pub(crate) struct PredictionPredicateCall {
997 pub(crate) rule_index: usize,
998 pub(crate) pred_index: usize,
999 pub(crate) rule_calls: Vec<PredictionRuleCall>,
1000}
1001
1002#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
1003struct PredictionSemanticProvenance {
1004 active_rule_calls: Vec<PredictionRuleCall>,
1005 predicate_calls: Vec<PredictionPredicateCall>,
1006}
1007
1008#[repr(transparent)]
1009#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
1010pub(crate) struct PredictionSemanticProvenanceId(u32);
1011
1012#[derive(Debug, Default)]
1013pub(crate) struct PredictionSemanticProvenanceArena {
1014 records: Vec<PredictionSemanticProvenance>,
1015 interner_heads: FxHashMap<u64, PredictionSemanticProvenanceId>,
1016 interner_next: Vec<Option<PredictionSemanticProvenanceId>>,
1017}
1018
1019impl PredictionSemanticProvenanceArena {
1020 pub(crate) fn enter_rule(
1021 &mut self,
1022 id: PredictionSemanticProvenanceId,
1023 source_state: usize,
1024 rule_index: usize,
1025 ) -> PredictionSemanticProvenanceId {
1026 let mut provenance = self.get(id).cloned().unwrap_or_default();
1027 provenance.active_rule_calls.push(PredictionRuleCall {
1028 source_state,
1029 rule_index,
1030 });
1031 self.intern(provenance)
1032 }
1033
1034 pub(crate) fn exit_rule(
1035 &mut self,
1036 id: PredictionSemanticProvenanceId,
1037 ) -> PredictionSemanticProvenanceId {
1038 let Some(mut provenance) = self.get(id).cloned() else {
1039 return PredictionSemanticProvenanceId::default();
1040 };
1041 provenance.active_rule_calls.pop();
1042 self.intern(provenance)
1043 }
1044
1045 pub(crate) fn record_predicate(
1046 &mut self,
1047 id: PredictionSemanticProvenanceId,
1048 rule_index: usize,
1049 pred_index: usize,
1050 ) -> PredictionSemanticProvenanceId {
1051 let mut provenance = self.get(id).cloned().unwrap_or_default();
1052 let call = PredictionPredicateCall {
1053 rule_index,
1054 pred_index,
1055 rule_calls: provenance.active_rule_calls.clone(),
1056 };
1057 if !provenance.predicate_calls.contains(&call) {
1058 provenance.predicate_calls.push(call);
1059 }
1060 self.intern(provenance)
1061 }
1062
1063 pub(crate) fn predicate_calls(
1064 &self,
1065 id: PredictionSemanticProvenanceId,
1066 ) -> &[PredictionPredicateCall] {
1067 self.get(id)
1068 .map_or(&[], |provenance| provenance.predicate_calls.as_slice())
1069 }
1070
1071 pub(crate) const fn len(&self) -> usize {
1072 self.records.len()
1073 }
1074
1075 pub(crate) fn retained_bytes(&self) -> usize {
1076 self.records.capacity() * size_of::<PredictionSemanticProvenance>()
1077 + self
1078 .records
1079 .iter()
1080 .map(|provenance| {
1081 provenance.active_rule_calls.capacity() * size_of::<PredictionRuleCall>()
1082 + provenance.predicate_calls.capacity()
1083 * size_of::<PredictionPredicateCall>()
1084 + provenance
1085 .predicate_calls
1086 .iter()
1087 .map(|call| {
1088 call.rule_calls.capacity() * size_of::<PredictionRuleCall>()
1089 })
1090 .sum::<usize>()
1091 })
1092 .sum::<usize>()
1093 + self.interner_heads.capacity() * size_of::<(u64, PredictionSemanticProvenanceId)>()
1094 + self.interner_next.capacity() * size_of::<Option<PredictionSemanticProvenanceId>>()
1095 }
1096
1097 fn get(&self, id: PredictionSemanticProvenanceId) -> Option<&PredictionSemanticProvenance> {
1098 let index = id.0.checked_sub(1)?;
1099 self.records.get(usize::try_from(index).ok()?)
1100 }
1101
1102 fn find_interned(
1103 &self,
1104 cached_hash: u64,
1105 provenance: &PredictionSemanticProvenance,
1106 ) -> Option<PredictionSemanticProvenanceId> {
1107 let mut candidate = self.interner_heads.get(&cached_hash).copied();
1108 while let Some(id) = candidate {
1109 let index = usize::try_from(id.0.checked_sub(1)?).ok()?;
1110 if self.records.get(index) == Some(provenance) {
1111 return Some(id);
1112 }
1113 candidate = self.interner_next.get(index).copied().flatten();
1114 }
1115 None
1116 }
1117
1118 fn intern(
1119 &mut self,
1120 provenance: PredictionSemanticProvenance,
1121 ) -> PredictionSemanticProvenanceId {
1122 if provenance.active_rule_calls.is_empty() && provenance.predicate_calls.is_empty() {
1123 return PredictionSemanticProvenanceId::default();
1124 }
1125 let mut hasher = PredictionFxHasher::default();
1126 provenance.hash(&mut hasher);
1127 let cached_hash = hasher.finish();
1128 if let Some(id) = self.find_interned(cached_hash, &provenance) {
1129 return id;
1130 }
1131 let id = PredictionSemanticProvenanceId(
1132 u32::try_from(self.records.len() + 1)
1133 .expect("prediction semantic provenance arena exhausted"),
1134 );
1135 assert!(
1136 id.0 <= ATN_CONFIG_PROVENANCE_MASK,
1137 "prediction semantic provenance arena exhausted"
1138 );
1139 let previous = self.interner_heads.insert(cached_hash, id);
1140 self.records.push(provenance);
1141 self.interner_next.push(previous);
1142 id
1143 }
1144}
1145
1146const ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED: u32 = 1 << 31;
1147const ATN_CONFIG_PROVENANCE_MASK: u32 = !ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED;
1148
1149#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1150pub(crate) struct AtnConfig {
1151 pub(crate) state: usize,
1152 pub(crate) alt: usize,
1153 pub(crate) context: ContextId,
1154 semantic_context: SemanticContextId,
1155 pub(crate) reaches_into_outer_context: usize,
1156 semantic_provenance_and_flags: u32,
1157 #[cfg(debug_assertions)]
1158 context_generation: u64,
1159}
1160
1161impl AtnConfig {
1162 pub(crate) fn new(state: usize, alt: usize, context: ContextId, arena: &ContextArena) -> Self {
1163 arena.assert_valid(context);
1164 Self {
1165 state,
1166 alt,
1167 context,
1168 semantic_context: SemanticContextId::default(),
1169 reaches_into_outer_context: 0,
1170 semantic_provenance_and_flags: 0,
1171 #[cfg(debug_assertions)]
1172 context_generation: arena.generation(),
1173 }
1174 }
1175
1176 #[must_use]
1177 #[cfg(test)]
1178 pub(crate) fn with_semantic_context(
1179 mut self,
1180 semantic_context: SemanticContext,
1181 arena: &mut SemanticContextArena,
1182 ) -> Self {
1183 self.semantic_context = arena.intern(semantic_context);
1184 self
1185 }
1186
1187 pub(crate) const fn semantic_context_id(&self) -> SemanticContextId {
1188 self.semantic_context
1189 }
1190
1191 pub(crate) fn semantic_context<'a>(
1192 &self,
1193 arena: &'a SemanticContextArena,
1194 ) -> &'a SemanticContext {
1195 arena.get(self.semantic_context)
1196 }
1197
1198 pub(crate) const fn has_semantic_context(&self) -> bool {
1199 self.semantic_context.0 != 0
1200 }
1201
1202 pub(crate) fn set_semantic_context(
1203 &mut self,
1204 semantic_context: SemanticContextId,
1205 arena: &SemanticContextArena,
1206 ) {
1207 arena.assert_valid(semantic_context);
1208 self.semantic_context = semantic_context;
1209 }
1210
1211 pub(crate) fn set_context(&mut self, context: ContextId, arena: &ContextArena) {
1212 arena.assert_valid(context);
1213 self.context = context;
1214 #[cfg(debug_assertions)]
1215 {
1216 self.context_generation = arena.generation();
1217 }
1218 }
1219
1220 pub(crate) fn moved_to(&self, state: usize, context: ContextId, arena: &ContextArena) -> Self {
1221 let mut moved = Self::new(state, self.alt, context, arena);
1222 moved.semantic_context = self.semantic_context;
1223 moved.reaches_into_outer_context = self.reaches_into_outer_context;
1224 moved.semantic_provenance_and_flags = self.semantic_provenance_and_flags;
1225 moved
1226 }
1227
1228 pub(crate) fn enter_prediction_rule(
1229 &mut self,
1230 arena: &mut PredictionSemanticProvenanceArena,
1231 source_state: usize,
1232 rule_index: usize,
1233 ) {
1234 let id = arena.enter_rule(self.semantic_provenance_id(), source_state, rule_index);
1235 self.set_semantic_provenance_id(id);
1236 }
1237
1238 pub(crate) fn exit_prediction_rule(&mut self, arena: &mut PredictionSemanticProvenanceArena) {
1239 let id = arena.exit_rule(self.semantic_provenance_id());
1240 self.set_semantic_provenance_id(id);
1241 }
1242
1243 pub(crate) fn record_prediction_predicate(
1244 &mut self,
1245 arena: &mut PredictionSemanticProvenanceArena,
1246 rule_index: usize,
1247 pred_index: usize,
1248 ) {
1249 let id = arena.record_predicate(self.semantic_provenance_id(), rule_index, pred_index);
1250 self.set_semantic_provenance_id(id);
1251 }
1252
1253 pub(crate) const fn semantic_provenance_id(&self) -> PredictionSemanticProvenanceId {
1254 PredictionSemanticProvenanceId(
1255 self.semantic_provenance_and_flags & ATN_CONFIG_PROVENANCE_MASK,
1256 )
1257 }
1258
1259 pub(crate) const fn semantic_provenance_and_flags(&self) -> u32 {
1260 self.semantic_provenance_and_flags
1261 }
1262
1263 fn set_semantic_provenance_id(&mut self, id: PredictionSemanticProvenanceId) {
1264 debug_assert_eq!(id.0 & ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED, 0);
1265 self.semantic_provenance_and_flags =
1266 (self.semantic_provenance_and_flags & ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED) | id.0;
1267 }
1268
1269 pub(crate) const fn precedence_filter_suppressed(&self) -> bool {
1270 self.semantic_provenance_and_flags & ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED != 0
1271 }
1272
1273 pub(crate) const fn suppress_precedence_filter(&mut self) {
1274 self.semantic_provenance_and_flags |= ATN_CONFIG_PRECEDENCE_FILTER_SUPPRESSED;
1275 }
1276
1277 pub(crate) const fn merge_precedence_filter_suppression(&mut self, other: &Self) {
1278 if other.precedence_filter_suppressed() {
1279 self.suppress_precedence_filter();
1280 }
1281 }
1282
1283 pub(crate) fn assert_store(&self, arena: &ContextArena) {
1284 arena.assert_valid(self.context);
1285 #[cfg(debug_assertions)]
1286 assert_eq!(
1287 self.context_generation,
1288 arena.generation(),
1289 "ATN config carries a ContextId from another prediction store"
1290 );
1291 }
1292}
1293
1294#[derive(Clone, Debug, Default)]
1295pub(crate) struct AtnConfigSet {
1296 configs: Vec<AtnConfig>,
1297 config_index: FxHashMap<AtnConfigKey, usize>,
1298 full_context: bool,
1299 unique_alt: Option<usize>,
1300 conflicting_alts: BTreeSet<usize>,
1301 has_semantic_context: bool,
1302 dips_into_outer_context: bool,
1303 readonly: bool,
1304}
1305
1306impl AtnConfigSet {
1307 pub(crate) fn new() -> Self {
1308 Self::default()
1309 }
1310
1311 pub(crate) fn new_full_context(full_context: bool) -> Self {
1312 Self {
1313 configs: Vec::new(),
1314 config_index: FxHashMap::default(),
1315 full_context,
1316 unique_alt: None,
1317 conflicting_alts: BTreeSet::new(),
1318 has_semantic_context: false,
1319 dips_into_outer_context: false,
1320 readonly: false,
1321 }
1322 }
1323
1324 pub(crate) fn add(
1326 &mut self,
1327 config: AtnConfig,
1328 arena: &mut ContextArena,
1329 workspace: &mut PredictionWorkspace,
1330 ) -> bool {
1331 assert!(!self.readonly, "cannot mutate readonly ATN config set");
1332 config.assert_store(arena);
1333 #[cfg(feature = "perf-counters")]
1334 crate::perf::record_config_add_call();
1335 if config.has_semantic_context() {
1336 self.has_semantic_context = true;
1337 }
1338 if config.reaches_into_outer_context > 0 {
1339 self.dips_into_outer_context = true;
1340 }
1341 let key = AtnConfigKey::from(&config);
1342 if let Some(existing_index) = self.config_index.get(&key).copied() {
1343 #[cfg(feature = "perf-counters")]
1344 crate::perf::record_config_merge();
1345 let existing = &mut self.configs[existing_index];
1346 existing.assert_store(arena);
1347 existing.context = arena.merge(
1348 existing.context,
1349 config.context,
1350 !self.full_context,
1351 workspace,
1352 );
1353 existing.reaches_into_outer_context = existing
1354 .reaches_into_outer_context
1355 .max(config.reaches_into_outer_context);
1356 existing.merge_precedence_filter_suppression(&config);
1357 self.conflicting_alts.clear();
1358 false
1359 } else {
1360 let index = self.configs.len();
1361 self.config_index.insert(key, index);
1362 self.configs.push(config);
1363 #[cfg(feature = "perf-counters")]
1364 crate::perf::record_config_insert(self.configs.len());
1365 self.unique_alt = None;
1366 self.conflicting_alts.clear();
1367 true
1368 }
1369 }
1370
1371 pub(crate) fn configs(&self) -> &[AtnConfig] {
1372 &self.configs
1373 }
1374
1375 pub(crate) fn into_configs(self) -> Vec<AtnConfig> {
1376 self.configs
1377 }
1378
1379 pub(crate) const fn is_empty(&self) -> bool {
1380 self.configs.is_empty()
1381 }
1382
1383 pub(crate) const fn len(&self) -> usize {
1384 self.configs.len()
1385 }
1386
1387 pub(crate) fn set_readonly(&mut self, readonly: bool) {
1388 self.readonly = readonly;
1389 if readonly {
1390 self.config_index = FxHashMap::default();
1391 self.conflicting_alts.clear();
1392 }
1393 }
1394
1395 pub(crate) const fn full_context(&self) -> bool {
1396 self.full_context
1397 }
1398
1399 pub(crate) const fn has_semantic_context(&self) -> bool {
1400 self.has_semantic_context
1401 }
1402
1403 pub(crate) fn unique_alt(&mut self) -> Option<usize> {
1404 if self.unique_alt.is_none() {
1405 self.unique_alt = unique_alt(self.configs());
1406 }
1407 self.unique_alt
1408 }
1409
1410 pub(crate) fn alts(&self) -> BTreeSet<usize> {
1411 self.configs.iter().map(|config| config.alt).collect()
1412 }
1413
1414 pub(crate) fn conflicting_alt_subsets(&self) -> Vec<BTreeSet<usize>> {
1415 conflicting_alt_subsets(self.configs())
1416 }
1417
1418 pub(crate) fn conflicting_alts(&mut self) -> BTreeSet<usize> {
1419 if self.conflicting_alts.is_empty() {
1420 self.conflicting_alts = self
1421 .conflicting_alt_subsets()
1422 .into_iter()
1423 .filter(|alts| alts.len() > 1)
1424 .flatten()
1425 .collect();
1426 }
1427 self.conflicting_alts.clone()
1428 }
1429
1430 pub(crate) fn remap_store_ids(
1431 &mut self,
1432 context_remap: &[ContextId],
1433 semantic_context_remap: &[SemanticContextId],
1434 contexts: &ContextArena,
1435 semantic_contexts: &SemanticContextArena,
1436 ) {
1437 for config in &mut self.configs {
1438 let index = usize::try_from(config.context.0).expect("u32 context ID fits usize");
1439 config.set_context(
1440 *context_remap
1441 .get(index)
1442 .expect("every imported context ID has a remap"),
1443 contexts,
1444 );
1445 let semantic_index = usize::try_from(config.semantic_context_id().0)
1446 .expect("u32 semantic-context ID fits usize");
1447 config.set_semantic_context(
1448 *semantic_context_remap
1449 .get(semantic_index)
1450 .expect("every imported semantic-context ID has a remap"),
1451 semantic_contexts,
1452 );
1453 }
1454 self.config_index.clear();
1455 if !self.readonly {
1456 for (index, config) in self.configs.iter().enumerate() {
1457 self.config_index.insert(AtnConfigKey::from(config), index);
1458 }
1459 }
1460 }
1461
1462 pub(crate) fn fingerprint(&self) -> u64 {
1463 let mut hasher = PredictionFxHasher::default();
1464 self.configs.hash(&mut hasher);
1465 self.full_context.hash(&mut hasher);
1466 self.has_semantic_context.hash(&mut hasher);
1467 self.dips_into_outer_context.hash(&mut hasher);
1468 hasher.finish()
1469 }
1470
1471 pub(crate) fn retained_bytes(&self) -> usize {
1472 self.configs.capacity() * size_of::<AtnConfig>()
1473 + self.config_index.capacity() * size_of::<(AtnConfigKey, usize)>()
1474 }
1475}
1476
1477impl PartialEq for AtnConfigSet {
1478 fn eq(&self, other: &Self) -> bool {
1479 self.configs == other.configs
1480 && self.full_context == other.full_context
1481 && self.has_semantic_context == other.has_semantic_context
1482 && self.dips_into_outer_context == other.dips_into_outer_context
1483 }
1484}
1485
1486impl Eq for AtnConfigSet {}
1487
1488impl Ord for AtnConfigSet {
1489 fn cmp(&self, other: &Self) -> Ordering {
1490 self.configs
1491 .cmp(&other.configs)
1492 .then_with(|| self.full_context.cmp(&other.full_context))
1493 .then_with(|| self.has_semantic_context.cmp(&other.has_semantic_context))
1494 .then_with(|| {
1495 self.dips_into_outer_context
1496 .cmp(&other.dips_into_outer_context)
1497 })
1498 }
1499}
1500
1501impl PartialOrd for AtnConfigSet {
1502 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1503 Some(self.cmp(other))
1504 }
1505}
1506
1507#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1508struct AtnConfigKey {
1509 state: usize,
1510 alt: usize,
1511 semantic_context: SemanticContextId,
1512 semantic_provenance: PredictionSemanticProvenanceId,
1513}
1514
1515impl From<&AtnConfig> for AtnConfigKey {
1516 fn from(config: &AtnConfig) -> Self {
1517 Self {
1518 state: config.state,
1519 alt: config.alt,
1520 semantic_context: config.semantic_context_id(),
1521 semantic_provenance: config.semantic_provenance_id(),
1522 }
1523 }
1524}
1525
1526pub(crate) fn unique_alt(configs: &[AtnConfig]) -> Option<usize> {
1527 let mut alt = None;
1528 for config in configs {
1529 match alt {
1530 None => alt = Some(config.alt),
1531 Some(existing) if existing == config.alt => {}
1532 Some(_) => return None,
1533 }
1534 }
1535 alt
1536}
1537
1538pub(crate) fn conflicting_alt_subsets(configs: &[AtnConfig]) -> Vec<BTreeSet<usize>> {
1539 let mut by_state_context = FxHashMap::<(usize, ContextId), BTreeSet<usize>>::default();
1540 for config in configs {
1541 by_state_context
1542 .entry((config.state, config.context))
1543 .or_default()
1544 .insert(config.alt);
1545 }
1546 by_state_context.into_values().collect()
1547}
1548
1549pub(crate) fn all_subsets_conflict(alt_subsets: &[BTreeSet<usize>]) -> bool {
1550 alt_subsets.iter().all(|alts| alts.len() > 1)
1551}
1552
1553pub(crate) fn all_subsets_equal(alt_subsets: &[BTreeSet<usize>]) -> bool {
1554 let mut subsets = alt_subsets.iter();
1555 let Some(first) = subsets.next() else {
1556 return true;
1557 };
1558 subsets.all(|alts| alts == first)
1559}
1560
1561pub(crate) fn single_viable_alt(alt_subsets: &[BTreeSet<usize>]) -> Option<usize> {
1562 let mut result = None;
1563 for alts in alt_subsets {
1564 let min_alt = alts.iter().next().copied()?;
1565 match result {
1566 None => result = Some(min_alt),
1567 Some(existing) if existing == min_alt => {}
1568 Some(_) => return None,
1569 }
1570 }
1571 result
1572}
1573
1574#[derive(Clone, Debug, Eq, PartialEq)]
1575pub(crate) struct SllConflict {
1576 pub(crate) alts: BTreeSet<usize>,
1577 pub(crate) exact: bool,
1578 pub(crate) from_context_containment: bool,
1579}
1580
1581pub(crate) fn has_sll_conflict_terminating_prediction(
1582 configs: &AtnConfigSet,
1583 is_rule_stop_state: impl Fn(usize) -> bool,
1584) -> bool {
1585 if configs
1586 .configs()
1587 .iter()
1588 .all(|config| is_rule_stop_state(config.state))
1589 {
1590 return true;
1591 }
1592 let alt_subsets = configs.conflicting_alt_subsets();
1593 alt_subsets.iter().any(|alts| alts.len() > 1)
1594 && !has_state_associated_with_one_alt(configs.configs())
1595}
1596
1597pub(crate) fn exact_context_sll_conflict(
1598 configs: &AtnConfigSet,
1599 arena: &mut ContextArena,
1600 workspace: &mut PredictionWorkspace,
1601 is_rule_stop_state: impl Fn(usize) -> bool,
1602) -> Option<SllConflict> {
1603 debug_assert!(!configs.full_context());
1604 if configs.len() <= 1 || unique_alt(configs.configs()).is_some() {
1605 return None;
1606 }
1607
1608 let state_key = |config: &AtnConfig| {
1609 if is_rule_stop_state(config.state) {
1610 None
1611 } else {
1612 Some(config.state)
1613 }
1614 };
1615 let mut sorted = configs.configs().iter().collect::<Vec<_>>();
1616 sorted.sort_unstable_by_key(|config| (state_key(config), config.alt));
1617
1618 let min_alt = sorted.first()?.alt;
1619 let mut state_start = 0;
1620 let mut represented_alts = Vec::new();
1621 let mut exact = !configs.dips_into_outer_context;
1622 let mut alts = BTreeSet::from([min_alt]);
1623
1624 while state_start < sorted.len() {
1625 let current_state = state_key(sorted[state_start]);
1626 let mut state_end = state_start + 1;
1627 while state_end < sorted.len() && state_key(sorted[state_end]) == current_state {
1628 state_end += 1;
1629 }
1630 if sorted[state_start].alt != min_alt {
1631 return None;
1632 }
1633
1634 let mut current_alts = Vec::new();
1635 let mut alt_start = state_start;
1636 let mut joined_min_context = None;
1637 while alt_start < state_end {
1638 let current_alt = sorted[alt_start].alt;
1639 current_alts.push(current_alt);
1640 alts.insert(current_alt);
1641
1642 let mut alt_end = alt_start + 1;
1643 while alt_end < state_end && sorted[alt_end].alt == current_alt {
1644 alt_end += 1;
1645 }
1646 let joined_context = sorted[alt_start + 1..alt_end]
1647 .iter()
1648 .fold(sorted[alt_start].context, |joined, config| {
1649 arena.merge(joined, config.context, true, workspace)
1650 });
1651 if current_alt == min_alt {
1652 joined_min_context = Some(joined_context);
1653 } else {
1654 let min_context =
1655 joined_min_context.expect("minimum alternative starts every state group");
1656 if arena.merge(min_context, joined_context, true, workspace) != min_context {
1657 return None;
1658 }
1659 exact &= min_context == joined_context;
1660 }
1661 alt_start = alt_end;
1662 }
1663
1664 if represented_alts.is_empty() {
1665 represented_alts = current_alts;
1666 } else {
1667 exact &= represented_alts == current_alts;
1668 }
1669 state_start = state_end;
1670 }
1671
1672 Some(SllConflict {
1673 alts,
1674 exact,
1675 from_context_containment: true,
1676 })
1677}
1678
1679fn has_state_associated_with_one_alt(configs: &[AtnConfig]) -> bool {
1680 let mut by_state = BTreeMap::<usize, BTreeSet<usize>>::new();
1681 for config in configs {
1682 by_state.entry(config.state).or_default().insert(config.alt);
1683 }
1684 by_state.values().any(|alts| alts.len() == 1)
1685}
1686
1687#[cfg(test)]
1688#[allow(clippy::disallowed_methods)] mod tests {
1690 use super::*;
1691
1692 #[test]
1693 fn arena_interns_singletons_without_per_context_objects() {
1694 let mut arena = ContextArena::new();
1695 let first = arena.singleton(EMPTY_CONTEXT, 7);
1696 let second = arena.singleton(EMPTY_CONTEXT, 7);
1697
1698 assert_eq!(first, second);
1699 assert_eq!(arena.stats().singleton_contexts, 1);
1700 assert_eq!(arena.stats().interner_hits, 1);
1701 }
1702
1703 #[test]
1704 fn array_interner_verifies_payload_after_hash_collision() {
1705 let mut arena = ContextArena::new();
1706 let first_parent = arena.singleton(EMPTY_CONTEXT, 1);
1707 let second_parent = arena.singleton(EMPTY_CONTEXT, 2);
1708 let expected = [(first_parent, 10), (second_parent, 20)];
1709 let colliding = [(second_parent, 10), (first_parent, 20)];
1710 let cached_hash = prediction_context_array_hash(&arena, &expected);
1711 let start = u32::try_from(arena.array_parents.len()).expect("pool index fits u32");
1712 arena
1713 .array_parents
1714 .extend(colliding.iter().map(|(parent, _)| *parent));
1715 arena
1716 .array_return_states
1717 .extend(colliding.iter().map(|(_, return_state)| *return_state));
1718 let collision = arena.push_record(ContextRecord {
1719 tag: ContextTag::Array,
1720 cached_hash,
1721 parent_or_start: start,
1722 return_state_or_len: 2,
1723 });
1724
1725 let interned = arena.intern_entries(&expected);
1726
1727 assert_ne!(interned, collision);
1728 assert_eq!(arena.entry(interned, 0), Some(expected[0]));
1729 assert_eq!(arena.entry(interned, 1), Some(expected[1]));
1730 }
1731
1732 #[test]
1733 fn merge_with_empty_preserves_full_context_empty_path() {
1734 let mut arena = ContextArena::new();
1735 let mut workspace = PredictionWorkspace::default();
1736 let singleton = arena.singleton(EMPTY_CONTEXT, 42);
1737
1738 let merged = arena.merge(singleton, EMPTY_CONTEXT, false, &mut workspace);
1739
1740 assert_eq!(arena.len(merged), 2);
1741 assert_eq!(arena.return_state(merged, 0), Some(42));
1742 assert_eq!(arena.parent(merged, 0), Some(EMPTY_CONTEXT));
1743 assert_eq!(arena.return_state(merged, 1), Some(EMPTY_RETURN_STATE));
1744 assert!(arena.has_empty_path(merged));
1745 }
1746
1747 #[test]
1748 fn wildcard_merge_collapses_to_empty() {
1749 let mut arena = ContextArena::new();
1750 let mut workspace = PredictionWorkspace::default();
1751 let singleton = arena.singleton(EMPTY_CONTEXT, 42);
1752
1753 assert_eq!(
1754 arena.merge(singleton, EMPTY_CONTEXT, true, &mut workspace),
1755 EMPTY_CONTEXT
1756 );
1757 }
1758
1759 #[test]
1760 fn merge_is_order_independent() {
1761 let mut arena = ContextArena::new();
1762 let mut workspace = PredictionWorkspace::default();
1763 let left_parent = arena.singleton(EMPTY_CONTEXT, 100);
1764 let right_parent = arena.singleton(EMPTY_CONTEXT, 200);
1765 let left = arena.singleton(left_parent, 7);
1766 let right = arena.singleton(right_parent, 7);
1767
1768 let left_right = arena.merge(left, right, false, &mut workspace);
1769 workspace.reset();
1770 let right_left = arena.merge(right, left, false, &mut workspace);
1771
1772 assert_eq!(left_right, right_left);
1773 assert_eq!(arena.len(left_right), 1);
1774 let merged_parent = arena.parent(left_right, 0).expect("merged parent");
1775 assert_eq!(arena.len(merged_parent), 2);
1776 assert_eq!(arena.return_state(merged_parent, 0), Some(100));
1777 assert_eq!(arena.return_state(merged_parent, 1), Some(200));
1778 }
1779
1780 #[test]
1781 fn import_remaps_contexts_into_destination_arena() {
1782 let mut source = ContextArena::new();
1783 let parent = source.singleton(EMPTY_CONTEXT, 3);
1784 let child = source.singleton(parent, 9);
1785 let mut destination = ContextArena::new();
1786 let mut workspace = PredictionWorkspace::default();
1787
1788 let remap = destination.import_all(&source, &mut workspace);
1789 let imported = remap[usize::try_from(child.0).expect("context ID fits usize")];
1790
1791 assert_eq!(destination.return_state(imported, 0), Some(9));
1792 let imported_parent = destination.parent(imported, 0).expect("parent");
1793 assert_eq!(destination.return_state(imported_parent, 0), Some(3));
1794 }
1795
1796 #[test]
1797 fn semantic_context_arena_interns_imports_and_accounts_for_payloads() {
1798 let predicate = SemanticContext::Predicate {
1799 rule_index: 2,
1800 pred_index: 3,
1801 context_dependent: true,
1802 };
1803 let mut source = SemanticContextArena::new();
1804 let predicate_id = source.intern(predicate.clone());
1805 assert_eq!(source.intern(predicate), predicate_id);
1806
1807 let combined_id = source.and(predicate_id, SemanticContext::Precedence { precedence: 4 });
1808 assert_ne!(combined_id, predicate_id);
1809 assert_eq!(source.len(), 3);
1810 assert!(source.retained_bytes() >= source.len() * size_of::<SemanticContext>());
1811
1812 let mut destination = SemanticContextArena::new();
1813 let distracting_id = destination.intern(SemanticContext::Precedence { precedence: 99 });
1814 assert_eq!(distracting_id, predicate_id, "both arenas allocate ID 1");
1815 let remap = destination.import_all(&source);
1816 let imported = remap[usize::try_from(combined_id.0).expect("semantic ID fits usize")];
1817 assert_eq!(destination.get(imported), source.get(combined_id));
1818 assert_ne!(imported, combined_id);
1819 }
1820
1821 #[test]
1822 fn semantic_context_arena_verifies_hash_collisions() {
1823 let mut arena = SemanticContextArena::new();
1824 let first = SemanticContext::Precedence { precedence: 1 };
1825 let first_id = arena.intern(first);
1826 let second = SemanticContext::Precedence { precedence: 2 };
1827 arena
1828 .interner_heads
1829 .insert(semantic_context_hash(&second), first_id);
1830
1831 let second_id = arena.intern(second.clone());
1832 assert_ne!(second_id, first_id);
1833 assert_eq!(arena.intern(second), second_id);
1834 }
1835
1836 #[test]
1837 fn config_set_merges_context_ids() {
1838 let mut arena = ContextArena::new();
1839 let mut workspace = PredictionWorkspace::default();
1840 let left = arena.singleton(EMPTY_CONTEXT, 1);
1841 let right = arena.singleton(EMPTY_CONTEXT, 2);
1842 let mut set = AtnConfigSet::new_full_context(true);
1843
1844 assert!(set.add(
1845 AtnConfig::new(1, 1, left, &arena),
1846 &mut arena,
1847 &mut workspace
1848 ));
1849 assert!(!set.add(
1850 AtnConfig::new(1, 1, right, &arena),
1851 &mut arena,
1852 &mut workspace
1853 ));
1854 assert_eq!(set.len(), 1);
1855 assert_eq!(arena.len(set.configs()[0].context), 2);
1856 }
1857
1858 #[test]
1859 fn exact_context_conflict_proves_containment_without_shared_context_ids() {
1860 let mut arena = ContextArena::new();
1861 let mut workspace = PredictionWorkspace::default();
1862 let first = arena.singleton(EMPTY_CONTEXT, 10);
1863 let second = arena.singleton(EMPTY_CONTEXT, 20);
1864 let containing = arena.merge(first, second, true, &mut workspace);
1865 let mut configs = AtnConfigSet::new();
1866 configs.add(
1867 AtnConfig::new(7, 1, containing, &arena),
1868 &mut arena,
1869 &mut workspace,
1870 );
1871 configs.add(
1872 AtnConfig::new(7, 2, first, &arena),
1873 &mut arena,
1874 &mut workspace,
1875 );
1876
1877 assert!(!has_sll_conflict_terminating_prediction(&configs, |_| {
1878 false
1879 }));
1880 insta::assert_debug_snapshot!(
1881 "exact_context_conflict_proves_containment_without_shared_context_ids",
1882 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false)
1883 );
1884 }
1885
1886 #[test]
1887 fn exact_context_conflict_joins_semantically_distinct_configs() {
1888 let mut arena = ContextArena::new();
1889 let mut semantic_contexts = SemanticContextArena::new();
1890 let mut workspace = PredictionWorkspace::default();
1891 let first = arena.singleton(EMPTY_CONTEXT, 10);
1892 let second = arena.singleton(EMPTY_CONTEXT, 20);
1893 let joined = arena.merge(first, second, true, &mut workspace);
1894 let predicate = |pred_index| SemanticContext::Predicate {
1895 rule_index: 0,
1896 pred_index,
1897 context_dependent: false,
1898 };
1899 let mut configs = AtnConfigSet::new();
1900 configs.add(
1901 AtnConfig::new(7, 1, first, &arena)
1902 .with_semantic_context(predicate(0), &mut semantic_contexts),
1903 &mut arena,
1904 &mut workspace,
1905 );
1906 configs.add(
1907 AtnConfig::new(7, 1, second, &arena)
1908 .with_semantic_context(predicate(1), &mut semantic_contexts),
1909 &mut arena,
1910 &mut workspace,
1911 );
1912 configs.add(
1913 AtnConfig::new(7, 2, joined, &arena),
1914 &mut arena,
1915 &mut workspace,
1916 );
1917
1918 assert!(!has_sll_conflict_terminating_prediction(&configs, |_| {
1919 false
1920 }));
1921 insta::assert_debug_snapshot!(
1922 "exact_context_conflict_joins_semantically_distinct_configs",
1923 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false)
1924 );
1925 }
1926
1927 #[test]
1928 fn exact_context_conflict_declines_a_non_contained_context() {
1929 let mut arena = ContextArena::new();
1930 let mut workspace = PredictionWorkspace::default();
1931 let first = arena.singleton(EMPTY_CONTEXT, 10);
1932 let second = arena.singleton(EMPTY_CONTEXT, 20);
1933 let mut configs = AtnConfigSet::new();
1934 configs.add(
1935 AtnConfig::new(7, 1, first, &arena),
1936 &mut arena,
1937 &mut workspace,
1938 );
1939 configs.add(
1940 AtnConfig::new(7, 2, second, &arena),
1941 &mut arena,
1942 &mut workspace,
1943 );
1944
1945 assert_eq!(
1946 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false),
1947 None
1948 );
1949 }
1950
1951 #[test]
1952 fn exact_context_conflict_requires_the_minimum_alt_in_every_state() {
1953 let mut arena = ContextArena::new();
1954 let mut workspace = PredictionWorkspace::default();
1955 let context = arena.singleton(EMPTY_CONTEXT, 10);
1956 let mut configs = AtnConfigSet::new();
1957 for (state, alt) in [(7, 1), (7, 2), (8, 2)] {
1958 configs.add(
1959 AtnConfig::new(state, alt, context, &arena),
1960 &mut arena,
1961 &mut workspace,
1962 );
1963 }
1964
1965 assert_eq!(
1966 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false),
1967 None
1968 );
1969 }
1970
1971 #[test]
1972 fn exact_context_conflict_marks_different_state_alt_sets_inexact() {
1973 let mut arena = ContextArena::new();
1974 let mut workspace = PredictionWorkspace::default();
1975 let context = arena.singleton(EMPTY_CONTEXT, 10);
1976 let mut configs = AtnConfigSet::new();
1977 for (state, alt) in [(7, 1), (7, 2), (8, 1), (8, 2), (8, 3)] {
1978 configs.add(
1979 AtnConfig::new(state, alt, context, &arena),
1980 &mut arena,
1981 &mut workspace,
1982 );
1983 }
1984
1985 insta::assert_debug_snapshot!(
1986 "exact_context_conflict_marks_different_state_alt_sets_inexact",
1987 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false)
1988 );
1989 }
1990
1991 #[test]
1992 fn exact_context_conflict_marks_outer_context_reach_inexact() {
1993 let mut arena = ContextArena::new();
1994 let mut workspace = PredictionWorkspace::default();
1995 let context = arena.singleton(EMPTY_CONTEXT, 10);
1996 let mut configs = AtnConfigSet::new();
1997 let mut outer = AtnConfig::new(7, 1, context, &arena);
1998 outer.reaches_into_outer_context = 1;
1999 configs.add(outer, &mut arena, &mut workspace);
2000 configs.add(
2001 AtnConfig::new(7, 2, context, &arena),
2002 &mut arena,
2003 &mut workspace,
2004 );
2005
2006 insta::assert_debug_snapshot!(
2007 "exact_context_conflict_marks_outer_context_reach_inexact",
2008 exact_context_sll_conflict(&configs, &mut arena, &mut workspace, |_| false)
2009 );
2010 }
2011
2012 #[test]
2013 fn predicate_provenance_is_idempotent_per_rule_path() {
2014 let arena = ContextArena::new();
2015 let mut provenance = PredictionSemanticProvenanceArena::default();
2016 let mut config = AtnConfig::new(1, 1, EMPTY_CONTEXT, &arena);
2017 config.enter_prediction_rule(&mut provenance, 4, 2);
2018 config.record_prediction_predicate(&mut provenance, 2, 3);
2019 let after_first = provenance
2020 .predicate_calls(config.semantic_provenance_id())
2021 .to_vec();
2022
2023 config.record_prediction_predicate(&mut provenance, 2, 3);
2024
2025 assert_eq!(
2026 provenance.predicate_calls(config.semantic_provenance_id()),
2027 after_first,
2028 "revisiting one predicate on the same rule path must not grow closure keys"
2029 );
2030 }
2031
2032 #[test]
2033 fn provenance_arena_stores_records_once_and_verifies_hash_collisions() {
2034 let mut arena = PredictionSemanticProvenanceArena::default();
2035 let first = PredictionSemanticProvenance {
2036 active_rule_calls: vec![PredictionRuleCall {
2037 source_state: 4,
2038 rule_index: 2,
2039 }],
2040 predicate_calls: Vec::new(),
2041 };
2042 let first_id = arena.intern(first.clone());
2043
2044 assert_eq!(arena.intern(first), first_id);
2045 assert_eq!(arena.records.len(), 1);
2046 assert_eq!(arena.interner_next.len(), 1);
2047
2048 let second = PredictionSemanticProvenance {
2049 active_rule_calls: vec![PredictionRuleCall {
2050 source_state: 5,
2051 rule_index: 3,
2052 }],
2053 predicate_calls: Vec::new(),
2054 };
2055 let mut hasher = PredictionFxHasher::default();
2056 second.hash(&mut hasher);
2057 arena.interner_heads.insert(hasher.finish(), first_id);
2058
2059 let second_id = arena.intern(second.clone());
2060 assert_ne!(second_id, first_id);
2061 assert_eq!(arena.intern(second), second_id);
2062 assert_eq!(arena.records.len(), 2);
2063 assert_eq!(arena.interner_next.len(), 2);
2064 }
2065
2066 #[test]
2067 fn config_set_keeps_distinct_prediction_provenance() {
2068 let mut arena = ContextArena::new();
2069 let semantic_contexts = SemanticContextArena::new();
2070 let mut provenance = PredictionSemanticProvenanceArena::default();
2071 let mut workspace = PredictionWorkspace::default();
2072 let mut first = AtnConfig::new(1, 1, EMPTY_CONTEXT, &arena);
2073 first.enter_prediction_rule(&mut provenance, 4, 2);
2074 let mut second = AtnConfig::new(1, 1, EMPTY_CONTEXT, &arena);
2075 second.enter_prediction_rule(&mut provenance, 5, 3);
2076 let mut set = AtnConfigSet::new();
2077
2078 assert!(set.add(first.clone(), &mut arena, &mut workspace));
2079 assert!(set.add(second, &mut arena, &mut workspace));
2080 assert!(!set.add(first, &mut arena, &mut workspace));
2081 assert_eq!(set.len(), 2);
2082 assert_eq!(set.config_index.len(), set.len());
2083
2084 set.remap_store_ids(
2085 &[EMPTY_CONTEXT],
2086 &[SemanticContextId::default()],
2087 &arena,
2088 &semantic_contexts,
2089 );
2090 assert_eq!(set.config_index.len(), set.len());
2091 }
2092
2093 #[cfg(target_pointer_width = "64")]
2094 #[test]
2095 fn parser_config_hot_path_layout_stays_compact() {
2096 let debug_generation = if cfg!(debug_assertions) {
2099 size_of::<u64>()
2100 } else {
2101 0
2102 };
2103 assert_eq!(size_of::<AtnConfig>(), 40 + debug_generation);
2104 assert_eq!(size_of::<AtnConfigKey>(), 24);
2105 }
2106
2107 #[test]
2108 fn workspace_drops_pathological_capacity() {
2109 let mut workspace = PredictionWorkspace::default();
2110 workspace
2111 .merge_cache
2112 .reserve(MAX_RETAINED_MERGE_CACHE_ENTRIES.saturating_mul(2));
2113 workspace
2114 .entries
2115 .reserve(MAX_RETAINED_CONTEXT_ENTRIES.saturating_mul(2));
2116 workspace.reset();
2117
2118 assert!(workspace.merge_cache.capacity() <= MAX_RETAINED_MERGE_CACHE_ENTRIES);
2119 assert!(workspace.entries.capacity() <= MAX_RETAINED_CONTEXT_ENTRIES);
2120 }
2121
2122 mod upstream_graph_nodes {
2123 use super::*;
2124 use std::collections::{BTreeSet, HashMap, VecDeque};
2125 use std::fmt::Write;
2126
2127 const EMPTY_WILDCARD_DOT: &str = concat!(
2128 "digraph G {\n",
2129 "rankdir=LR;\n",
2130 " s0[label=\"*\"];\n",
2131 "}\n",
2132 );
2133 const EMPTY_FULL_CONTEXT_DOT: &str = concat!(
2134 "digraph G {\n",
2135 "rankdir=LR;\n",
2136 " s0[label=\"$\"];\n",
2137 "}\n",
2138 );
2139 const X_EMPTY_FULL_CONTEXT_DOT: &str = concat!(
2140 "digraph G {\n",
2141 "rankdir=LR;\n",
2142 " s0[shape=record, label=\"<p0>|<p1>$\"];\n",
2143 " s1[label=\"$\"];\n",
2144 " s0:p0->s1[label=\"9\"];\n",
2145 "}\n",
2146 );
2147 const A_DOT: &str = concat!(
2148 "digraph G {\n",
2149 "rankdir=LR;\n",
2150 " s0[label=\"0\"];\n",
2151 " s1[label=\"*\"];\n",
2152 " s0->s1[label=\"1\"];\n",
2153 "}\n",
2154 );
2155 const A_EMPTY_AX_FULL_CONTEXT_DOT: &str = concat!(
2156 "digraph G {\n",
2157 "rankdir=LR;\n",
2158 " s0[label=\"0\"];\n",
2159 " s1[shape=record, label=\"<p0>|<p1>$\"];\n",
2160 " s2[label=\"$\"];\n",
2161 " s0->s1[label=\"1\"];\n",
2162 " s1:p0->s2[label=\"9\"];\n",
2163 "}\n",
2164 );
2165 const NESTED_FULL_CONTEXT_DOT: &str = concat!(
2166 "digraph G {\n",
2167 "rankdir=LR;\n",
2168 " s0[shape=record, label=\"<p0>|<p1>$\"];\n",
2169 " s1[shape=record, label=\"<p0>|<p1>$\"];\n",
2170 " s2[label=\"$\"];\n",
2171 " s0:p0->s1[label=\"8\"];\n",
2172 " s1:p0->s2[label=\"8\"];\n",
2173 "}\n",
2174 );
2175 const A_B_DOT: &str = concat!(
2176 "digraph G {\n",
2177 "rankdir=LR;\n",
2178 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2179 " s1[label=\"*\"];\n",
2180 " s0:p0->s1[label=\"1\"];\n",
2181 " s0:p1->s1[label=\"2\"];\n",
2182 "}\n",
2183 );
2184 const AX_AX_DOT: &str = concat!(
2185 "digraph G {\n",
2186 "rankdir=LR;\n",
2187 " s0[label=\"0\"];\n",
2188 " s1[label=\"1\"];\n",
2189 " s2[label=\"*\"];\n",
2190 " s0->s1[label=\"1\"];\n",
2191 " s1->s2[label=\"9\"];\n",
2192 "}\n",
2193 );
2194 const ABX_ABX_DOT: &str = concat!(
2195 "digraph G {\n",
2196 "rankdir=LR;\n",
2197 " s0[label=\"0\"];\n",
2198 " s1[label=\"1\"];\n",
2199 " s2[label=\"2\"];\n",
2200 " s3[label=\"*\"];\n",
2201 " s0->s1[label=\"1\"];\n",
2202 " s1->s2[label=\"2\"];\n",
2203 " s2->s3[label=\"9\"];\n",
2204 "}\n",
2205 );
2206 const ABX_ACX_DOT: &str = concat!(
2207 "digraph G {\n",
2208 "rankdir=LR;\n",
2209 " s0[label=\"0\"];\n",
2210 " s1[shape=record, label=\"<p0>|<p1>\"];\n",
2211 " s2[label=\"2\"];\n",
2212 " s3[label=\"*\"];\n",
2213 " s0->s1[label=\"1\"];\n",
2214 " s1:p0->s2[label=\"2\"];\n",
2215 " s1:p1->s2[label=\"3\"];\n",
2216 " s2->s3[label=\"9\"];\n",
2217 "}\n",
2218 );
2219 const AX_BX_DOT: &str = concat!(
2220 "digraph G {\n",
2221 "rankdir=LR;\n",
2222 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2223 " s1[label=\"1\"];\n",
2224 " s2[label=\"*\"];\n",
2225 " s0:p0->s1[label=\"1\"];\n",
2226 " s0:p1->s1[label=\"2\"];\n",
2227 " s1->s2[label=\"9\"];\n",
2228 "}\n",
2229 );
2230 const AX_BY_DOT: &str = concat!(
2231 "digraph G {\n",
2232 "rankdir=LR;\n",
2233 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2234 " s2[label=\"2\"];\n",
2235 " s3[label=\"*\"];\n",
2236 " s1[label=\"1\"];\n",
2237 " s0:p0->s1[label=\"1\"];\n",
2238 " s0:p1->s2[label=\"2\"];\n",
2239 " s2->s3[label=\"10\"];\n",
2240 " s1->s3[label=\"9\"];\n",
2241 "}\n",
2242 );
2243 const A_EMPTY_BX_DOT: &str = concat!(
2244 "digraph G {\n",
2245 "rankdir=LR;\n",
2246 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2247 " s2[label=\"2\"];\n",
2248 " s1[label=\"*\"];\n",
2249 " s0:p0->s1[label=\"1\"];\n",
2250 " s0:p1->s2[label=\"2\"];\n",
2251 " s2->s1[label=\"9\"];\n",
2252 "}\n",
2253 );
2254 const A_EMPTY_BX_FULL_CONTEXT_DOT: &str = concat!(
2255 "digraph G {\n",
2256 "rankdir=LR;\n",
2257 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2258 " s2[label=\"2\"];\n",
2259 " s1[label=\"$\"];\n",
2260 " s0:p0->s1[label=\"1\"];\n",
2261 " s0:p1->s2[label=\"2\"];\n",
2262 " s2->s1[label=\"9\"];\n",
2263 "}\n",
2264 );
2265 const AEX_BFX_DOT: &str = concat!(
2266 "digraph G {\n",
2267 "rankdir=LR;\n",
2268 " s0[shape=record, label=\"<p0>|<p1>\"];\n",
2269 " s2[label=\"2\"];\n",
2270 " s3[label=\"3\"];\n",
2271 " s4[label=\"*\"];\n",
2272 " s1[label=\"1\"];\n",
2273 " s0:p0->s1[label=\"1\"];\n",
2274 " s0:p1->s2[label=\"2\"];\n",
2275 " s2->s3[label=\"6\"];\n",
2276 " s3->s4[label=\"9\"];\n",
2277 " s1->s3[label=\"5\"];\n",
2278 "}\n",
2279 );
2280 const A_B_C_DOT: &str = concat!(
2281 "digraph G {\n",
2282 "rankdir=LR;\n",
2283 " s0[shape=record, label=\"<p0>|<p1>|<p2>\"];\n",
2284 " s1[label=\"*\"];\n",
2285 " s0:p0->s1[label=\"1\"];\n",
2286 " s0:p1->s1[label=\"2\"];\n",
2287 " s0:p2->s1[label=\"3\"];\n",
2288 "}\n",
2289 );
2290 const AAX_AAY_DOT: &str = concat!(
2291 "digraph G {\n",
2292 "rankdir=LR;\n",
2293 " s0[label=\"0\"];\n",
2294 " s1[shape=record, label=\"<p0>|<p1>\"];\n",
2295 " s2[label=\"*\"];\n",
2296 " s0->s1[label=\"1\"];\n",
2297 " s1:p0->s2[label=\"9\"];\n",
2298 " s1:p1->s2[label=\"10\"];\n",
2299 "}\n",
2300 );
2301 const AAXC_AAYD_DOT: &str = concat!(
2302 "digraph G {\n",
2303 "rankdir=LR;\n",
2304 " s0[shape=record, label=\"<p0>|<p1>|<p2>\"];\n",
2305 " s2[label=\"*\"];\n",
2306 " s1[shape=record, label=\"<p0>|<p1>\"];\n",
2307 " s0:p0->s1[label=\"1\"];\n",
2308 " s0:p1->s2[label=\"3\"];\n",
2309 " s0:p2->s2[label=\"4\"];\n",
2310 " s1:p0->s2[label=\"9\"];\n",
2311 " s1:p1->s2[label=\"10\"];\n",
2312 "}\n",
2313 );
2314 const AAUBV_ACWDX_DOT: &str = concat!(
2315 "digraph G {\n",
2316 "rankdir=LR;\n",
2317 " s0[shape=record, label=\"<p0>|<p1>|<p2>|<p3>\"];\n",
2318 " s4[label=\"4\"];\n",
2319 " s5[label=\"*\"];\n",
2320 " s3[label=\"3\"];\n",
2321 " s2[label=\"2\"];\n",
2322 " s1[label=\"1\"];\n",
2323 " s0:p0->s1[label=\"1\"];\n",
2324 " s0:p1->s2[label=\"2\"];\n",
2325 " s0:p2->s3[label=\"3\"];\n",
2326 " s0:p3->s4[label=\"4\"];\n",
2327 " s4->s5[label=\"9\"];\n",
2328 " s3->s5[label=\"8\"];\n",
2329 " s2->s5[label=\"7\"];\n",
2330 " s1->s5[label=\"6\"];\n",
2331 "}\n",
2332 );
2333 const AAUBV_ABVDX_DOT: &str = concat!(
2334 "digraph G {\n",
2335 "rankdir=LR;\n",
2336 " s0[shape=record, label=\"<p0>|<p1>|<p2>\"];\n",
2337 " s3[label=\"3\"];\n",
2338 " s4[label=\"*\"];\n",
2339 " s2[label=\"2\"];\n",
2340 " s1[label=\"1\"];\n",
2341 " s0:p0->s1[label=\"1\"];\n",
2342 " s0:p1->s2[label=\"2\"];\n",
2343 " s0:p2->s3[label=\"4\"];\n",
2344 " s3->s4[label=\"9\"];\n",
2345 " s2->s4[label=\"7\"];\n",
2346 " s1->s4[label=\"6\"];\n",
2347 "}\n",
2348 );
2349 const AAUBV_ABWDX_DOT: &str = concat!(
2350 "digraph G {\n",
2351 "rankdir=LR;\n",
2352 " s0[shape=record, label=\"<p0>|<p1>|<p2>\"];\n",
2353 " s3[label=\"3\"];\n",
2354 " s4[label=\"*\"];\n",
2355 " s2[shape=record, label=\"<p0>|<p1>\"];\n",
2356 " s1[label=\"1\"];\n",
2357 " s0:p0->s1[label=\"1\"];\n",
2358 " s0:p1->s2[label=\"2\"];\n",
2359 " s0:p2->s3[label=\"4\"];\n",
2360 " s3->s4[label=\"9\"];\n",
2361 " s2:p0->s4[label=\"7\"];\n",
2362 " s2:p1->s4[label=\"8\"];\n",
2363 " s1->s4[label=\"6\"];\n",
2364 "}\n",
2365 );
2366 const AAUBV_ABVDU_DOT: &str = concat!(
2367 "digraph G {\n",
2368 "rankdir=LR;\n",
2369 " s0[shape=record, label=\"<p0>|<p1>|<p2>\"];\n",
2370 " s2[label=\"2\"];\n",
2371 " s3[label=\"*\"];\n",
2372 " s1[label=\"1\"];\n",
2373 " s0:p0->s1[label=\"1\"];\n",
2374 " s0:p1->s2[label=\"2\"];\n",
2375 " s0:p2->s1[label=\"4\"];\n",
2376 " s2->s3[label=\"7\"];\n",
2377 " s1->s3[label=\"6\"];\n",
2378 "}\n",
2379 );
2380 const AAUBU_ACUDU_DOT: &str = concat!(
2381 "digraph G {\n",
2382 "rankdir=LR;\n",
2383 " s0[shape=record, label=\"<p0>|<p1>|<p2>|<p3>\"];\n",
2384 " s1[label=\"1\"];\n",
2385 " s2[label=\"*\"];\n",
2386 " s0:p0->s1[label=\"1\"];\n",
2387 " s0:p1->s1[label=\"2\"];\n",
2388 " s0:p2->s1[label=\"3\"];\n",
2389 " s0:p3->s1[label=\"4\"];\n",
2390 " s1->s2[label=\"6\"];\n",
2391 "}\n",
2392 );
2393
2394 #[derive(Clone, Copy)]
2395 enum ContextSpec {
2396 Empty,
2397 Chain(&'static [usize]),
2398 Array(&'static [&'static [usize]]),
2399 }
2400
2401 #[derive(Clone, Copy)]
2402 enum Scenario {
2403 Merge {
2404 left: ContextSpec,
2405 right: ContextSpec,
2406 },
2407 NestedFullContext,
2408 }
2409
2410 struct GraphCase {
2411 source_test: &'static str,
2412 logical_id: &'static str,
2413 scenario: Scenario,
2414 root_is_wildcard: bool,
2415 expected: &'static str,
2416 }
2417
2418 impl GraphCase {
2419 const fn merge(
2420 source_test: &'static str,
2421 logical_id: &'static str,
2422 left: ContextSpec,
2423 right: ContextSpec,
2424 root_is_wildcard: bool,
2425 expected: &'static str,
2426 ) -> Self {
2427 Self {
2428 source_test,
2429 logical_id,
2430 scenario: Scenario::Merge { left, right },
2431 root_is_wildcard,
2432 expected,
2433 }
2434 }
2435
2436 const fn nested_full_context(
2437 source_test: &'static str,
2438 logical_id: &'static str,
2439 expected: &'static str,
2440 ) -> Self {
2441 Self {
2442 source_test,
2443 logical_id,
2444 scenario: Scenario::NestedFullContext,
2445 root_is_wildcard: false,
2446 expected,
2447 }
2448 }
2449 }
2450
2451 const CASES: &[GraphCase] = &[
2452 GraphCase::merge(
2453 "test_$_$",
2454 "testgraphnodes-test-9ea85e6b69",
2455 ContextSpec::Empty,
2456 ContextSpec::Empty,
2457 true,
2458 EMPTY_WILDCARD_DOT,
2459 ),
2460 GraphCase::merge(
2461 "test_$_$_fullctx",
2462 "testgraphnodes-test-fullctx-3a6b2d8201",
2463 ContextSpec::Empty,
2464 ContextSpec::Empty,
2465 false,
2466 EMPTY_FULL_CONTEXT_DOT,
2467 ),
2468 GraphCase::merge(
2469 "test_x_$",
2470 "testgraphnodes-test-x-546922b23c",
2471 ContextSpec::Chain(&[9]),
2472 ContextSpec::Empty,
2473 true,
2474 EMPTY_WILDCARD_DOT,
2475 ),
2476 GraphCase::merge(
2477 "test_x_$_fullctx",
2478 "testgraphnodes-test-x-fullctx-7fdaaf473e",
2479 ContextSpec::Chain(&[9]),
2480 ContextSpec::Empty,
2481 false,
2482 X_EMPTY_FULL_CONTEXT_DOT,
2483 ),
2484 GraphCase::merge(
2485 "test_$_x",
2486 "testgraphnodes-test-x-546922b23c",
2487 ContextSpec::Empty,
2488 ContextSpec::Chain(&[9]),
2489 true,
2490 EMPTY_WILDCARD_DOT,
2491 ),
2492 GraphCase::merge(
2493 "test_$_x_fullctx",
2494 "testgraphnodes-test-x-fullctx-7fdaaf473e",
2495 ContextSpec::Empty,
2496 ContextSpec::Chain(&[9]),
2497 false,
2498 X_EMPTY_FULL_CONTEXT_DOT,
2499 ),
2500 GraphCase::merge(
2501 "test_a_a",
2502 "testgraphnodes-test-a-a-429589e373",
2503 ContextSpec::Chain(&[1]),
2504 ContextSpec::Chain(&[1]),
2505 true,
2506 A_DOT,
2507 ),
2508 GraphCase::merge(
2509 "test_a$_ax",
2510 "testgraphnodes-test-a-ax-fd976a340d",
2511 ContextSpec::Chain(&[1]),
2512 ContextSpec::Chain(&[9, 1]),
2513 true,
2514 A_DOT,
2515 ),
2516 GraphCase::merge(
2517 "test_a$_ax_fullctx",
2518 "testgraphnodes-test-a-ax-fullctx-502155fcf9",
2519 ContextSpec::Chain(&[1]),
2520 ContextSpec::Chain(&[9, 1]),
2521 false,
2522 A_EMPTY_AX_FULL_CONTEXT_DOT,
2523 ),
2524 GraphCase::merge(
2525 "test_ax$_a$",
2526 "testgraphnodes-test-ax-a-62a48f251b",
2527 ContextSpec::Chain(&[9, 1]),
2528 ContextSpec::Chain(&[1]),
2529 true,
2530 A_DOT,
2531 ),
2532 GraphCase::nested_full_context(
2533 "test_aa$_a$_$_fullCtx",
2534 "testgraphnodes-test-aa-a-fullctx-8e728ea773",
2535 NESTED_FULL_CONTEXT_DOT,
2536 ),
2537 GraphCase::merge(
2538 "test_ax$_a$_fullctx",
2539 "testgraphnodes-test-ax-a-fullctx-7ef9c1d6b2",
2540 ContextSpec::Chain(&[9, 1]),
2541 ContextSpec::Chain(&[1]),
2542 false,
2543 A_EMPTY_AX_FULL_CONTEXT_DOT,
2544 ),
2545 GraphCase::merge(
2546 "test_a_b",
2547 "testgraphnodes-test-a-b-080058428f",
2548 ContextSpec::Chain(&[1]),
2549 ContextSpec::Chain(&[2]),
2550 true,
2551 A_B_DOT,
2552 ),
2553 GraphCase::merge(
2554 "test_ax_ax_same",
2555 "testgraphnodes-test-ax-ax-same-1504dc3dd3",
2556 ContextSpec::Chain(&[9, 1]),
2557 ContextSpec::Chain(&[9, 1]),
2558 true,
2559 AX_AX_DOT,
2560 ),
2561 GraphCase::merge(
2562 "test_ax_ax",
2563 "testgraphnodes-test-ax-ax-48f57578fa",
2564 ContextSpec::Chain(&[9, 1]),
2565 ContextSpec::Chain(&[9, 1]),
2566 true,
2567 AX_AX_DOT,
2568 ),
2569 GraphCase::merge(
2570 "test_abx_abx",
2571 "testgraphnodes-test-abx-abx-77366e32e9",
2572 ContextSpec::Chain(&[9, 2, 1]),
2573 ContextSpec::Chain(&[9, 2, 1]),
2574 true,
2575 ABX_ABX_DOT,
2576 ),
2577 GraphCase::merge(
2578 "test_abx_acx",
2579 "testgraphnodes-test-abx-acx-a3af7f90fa",
2580 ContextSpec::Chain(&[9, 2, 1]),
2581 ContextSpec::Chain(&[9, 3, 1]),
2582 true,
2583 ABX_ACX_DOT,
2584 ),
2585 GraphCase::merge(
2586 "test_ax_bx_same",
2587 "testgraphnodes-test-ax-bx-same-d0506bf7a9",
2588 ContextSpec::Chain(&[9, 1]),
2589 ContextSpec::Chain(&[9, 2]),
2590 true,
2591 AX_BX_DOT,
2592 ),
2593 GraphCase::merge(
2594 "test_ax_bx",
2595 "testgraphnodes-test-ax-bx-1ea2df9a04",
2596 ContextSpec::Chain(&[9, 1]),
2597 ContextSpec::Chain(&[9, 2]),
2598 true,
2599 AX_BX_DOT,
2600 ),
2601 GraphCase::merge(
2602 "test_ax_by",
2603 "testgraphnodes-test-ax-by-47815d59d2",
2604 ContextSpec::Chain(&[9, 1]),
2605 ContextSpec::Chain(&[10, 2]),
2606 true,
2607 AX_BY_DOT,
2608 ),
2609 GraphCase::merge(
2610 "test_a$_bx",
2611 "testgraphnodes-test-a-bx-b15f7b876f",
2612 ContextSpec::Chain(&[1]),
2613 ContextSpec::Chain(&[9, 2]),
2614 true,
2615 A_EMPTY_BX_DOT,
2616 ),
2617 GraphCase::merge(
2618 "test_a$_bx_fullctx",
2619 "testgraphnodes-test-a-bx-fullctx-a35242b6cf",
2620 ContextSpec::Chain(&[1]),
2621 ContextSpec::Chain(&[9, 2]),
2622 false,
2623 A_EMPTY_BX_FULL_CONTEXT_DOT,
2624 ),
2625 GraphCase::merge(
2626 "test_aex_bfx",
2627 "testgraphnodes-test-aex-bfx-07ad9de126",
2628 ContextSpec::Chain(&[9, 5, 1]),
2629 ContextSpec::Chain(&[9, 6, 2]),
2630 true,
2631 AEX_BFX_DOT,
2632 ),
2633 GraphCase::merge(
2634 "test_A$_A$_fullctx",
2635 "testgraphnodes-test-a-a-fullctx-b023f64b6c",
2636 ContextSpec::Array(&[&[]]),
2637 ContextSpec::Array(&[&[]]),
2638 false,
2639 EMPTY_FULL_CONTEXT_DOT,
2640 ),
2641 GraphCase::merge(
2642 "test_Aab_Ac",
2643 "testgraphnodes-test-aab-ac-139c5b709d",
2644 ContextSpec::Array(&[&[1], &[2]]),
2645 ContextSpec::Array(&[&[3]]),
2646 true,
2647 A_B_C_DOT,
2648 ),
2649 GraphCase::merge(
2650 "test_Aa_Aa",
2651 "testgraphnodes-test-aa-aa-0a175c83db",
2652 ContextSpec::Array(&[&[1]]),
2653 ContextSpec::Array(&[&[1]]),
2654 true,
2655 A_DOT,
2656 ),
2657 GraphCase::merge(
2658 "test_Aa_Abc",
2659 "testgraphnodes-test-aa-abc-db12d99894",
2660 ContextSpec::Array(&[&[1]]),
2661 ContextSpec::Array(&[&[2], &[3]]),
2662 true,
2663 A_B_C_DOT,
2664 ),
2665 GraphCase::merge(
2666 "test_Aac_Ab",
2667 "testgraphnodes-test-aac-ab-ef785e17e7",
2668 ContextSpec::Array(&[&[1], &[3]]),
2669 ContextSpec::Array(&[&[2]]),
2670 true,
2671 A_B_C_DOT,
2672 ),
2673 GraphCase::merge(
2674 "test_Aab_Aa",
2675 "testgraphnodes-test-aab-aa-d90d8d54f0",
2676 ContextSpec::Array(&[&[1], &[2]]),
2677 ContextSpec::Array(&[&[1]]),
2678 true,
2679 A_B_DOT,
2680 ),
2681 GraphCase::merge(
2682 "test_Aab_Ab",
2683 "testgraphnodes-test-aab-ab-e2d46352b4",
2684 ContextSpec::Array(&[&[1], &[2]]),
2685 ContextSpec::Array(&[&[2]]),
2686 true,
2687 A_B_DOT,
2688 ),
2689 GraphCase::merge(
2690 "test_Aax_Aby",
2691 "testgraphnodes-test-aax-aby-cccf935759",
2692 ContextSpec::Array(&[&[9, 1]]),
2693 ContextSpec::Array(&[&[10, 2]]),
2694 true,
2695 AX_BY_DOT,
2696 ),
2697 GraphCase::merge(
2698 "test_Aax_Aay",
2699 "testgraphnodes-test-aax-aay-c0f9b80842",
2700 ContextSpec::Array(&[&[9, 1]]),
2701 ContextSpec::Array(&[&[10, 1]]),
2702 true,
2703 AAX_AAY_DOT,
2704 ),
2705 GraphCase::merge(
2706 "test_Aaxc_Aayd",
2707 "testgraphnodes-test-aaxc-aayd-a73533f64d",
2708 ContextSpec::Array(&[&[9, 1], &[3]]),
2709 ContextSpec::Array(&[&[10, 1], &[4]]),
2710 true,
2711 AAXC_AAYD_DOT,
2712 ),
2713 GraphCase::merge(
2714 "test_Aaubv_Acwdx",
2715 "testgraphnodes-test-aaubv-acwdx-f479c849df",
2716 ContextSpec::Array(&[&[6, 1], &[7, 2]]),
2717 ContextSpec::Array(&[&[8, 3], &[9, 4]]),
2718 true,
2719 AAUBV_ACWDX_DOT,
2720 ),
2721 GraphCase::merge(
2722 "test_Aaubv_Abvdx",
2723 "testgraphnodes-test-aaubv-abvdx-01eb5714fe",
2724 ContextSpec::Array(&[&[6, 1], &[7, 2]]),
2725 ContextSpec::Array(&[&[7, 2], &[9, 4]]),
2726 true,
2727 AAUBV_ABVDX_DOT,
2728 ),
2729 GraphCase::merge(
2730 "test_Aaubv_Abwdx",
2731 "testgraphnodes-test-aaubv-abwdx-7953c9b489",
2732 ContextSpec::Array(&[&[6, 1], &[7, 2]]),
2733 ContextSpec::Array(&[&[8, 2], &[9, 4]]),
2734 true,
2735 AAUBV_ABWDX_DOT,
2736 ),
2737 GraphCase::merge(
2738 "test_Aaubv_Abvdu",
2739 "testgraphnodes-test-aaubv-abvdu-ecc8850384",
2740 ContextSpec::Array(&[&[6, 1], &[7, 2]]),
2741 ContextSpec::Array(&[&[7, 2], &[6, 4]]),
2742 true,
2743 AAUBV_ABVDU_DOT,
2744 ),
2745 GraphCase::merge(
2746 "test_Aaubu_Acudu",
2747 "testgraphnodes-test-aaubu-acudu-7cb798b616",
2748 ContextSpec::Array(&[&[6, 1], &[6, 2]]),
2749 ContextSpec::Array(&[&[6, 3], &[6, 4]]),
2750 true,
2751 AAUBU_ACUDU_DOT,
2752 ),
2753 ];
2754
2755 fn build_chain(arena: &mut ContextArena, return_states: &[usize]) -> ContextId {
2756 let mut context = EMPTY_CONTEXT;
2757 for &return_state in return_states {
2758 context = arena.singleton(context, return_state);
2759 }
2760 context
2761 }
2762
2763 fn build_context(arena: &mut ContextArena, spec: ContextSpec) -> ContextId {
2764 match spec {
2765 ContextSpec::Empty => EMPTY_CONTEXT,
2766 ContextSpec::Chain(return_states) => build_chain(arena, return_states),
2767 ContextSpec::Array(chains) => {
2768 let mut entries = Vec::with_capacity(chains.len());
2769 for return_states in chains {
2770 let context = build_chain(arena, return_states);
2771 entries.push(arena.first_entry(context));
2772 }
2773 arena.intern_entries(&entries)
2774 }
2775 }
2776 }
2777
2778 fn run_case(case: &GraphCase) -> String {
2779 let mut arena = ContextArena::new();
2780 let mut workspace = PredictionWorkspace::default();
2781 let merged = match case.scenario {
2782 Scenario::Merge { left, right } => {
2783 let left = build_context(&mut arena, left);
2784 let right = build_context(&mut arena, right);
2785 arena.merge(left, right, case.root_is_wildcard, &mut workspace)
2786 }
2787 Scenario::NestedFullContext => {
2788 let child = arena.singleton(EMPTY_CONTEXT, 8);
2789 let right = arena.merge(EMPTY_CONTEXT, child, false, &mut workspace);
2790 let left = arena.singleton(right, 8);
2791 arena.merge(left, right, false, &mut workspace)
2792 }
2793 };
2794 render_dot(&arena, merged, case.root_is_wildcard)
2795 }
2796
2797 fn render_dot(arena: &ContextArena, context: ContextId, root_is_wildcard: bool) -> String {
2798 let mut nodes = String::new();
2799 let mut edges = String::new();
2800 let mut context_ids = HashMap::new();
2801 let mut work_list = VecDeque::new();
2802 context_ids.insert(context, 0);
2803 work_list.push_back(context);
2804
2805 while let Some(current) = work_list.pop_front() {
2806 let current_id = context_ids[¤t];
2807 let len = arena.len(current);
2808 write!(&mut nodes, " s{current_id}[").expect("write to string");
2809 if len > 1 {
2810 nodes.push_str("shape=record, ");
2811 }
2812 nodes.push_str("label=\"");
2813 if arena.is_empty(current) {
2814 nodes.push(if root_is_wildcard { '*' } else { '$' });
2815 } else if len > 1 {
2816 for index in 0..len {
2817 if index > 0 {
2818 nodes.push('|');
2819 }
2820 write!(&mut nodes, "<p{index}>").expect("write to string");
2821 if arena.return_state(current, index) == Some(EMPTY_RETURN_STATE) {
2822 nodes.push(if root_is_wildcard { '*' } else { '$' });
2823 }
2824 }
2825 } else {
2826 write!(&mut nodes, "{current_id}").expect("write to string");
2827 }
2828 nodes.push_str("\"];\n");
2829
2830 if arena.is_empty(current) {
2831 continue;
2832 }
2833 for index in 0..len {
2834 let return_state = arena
2835 .return_state(current, index)
2836 .expect("context entry in range");
2837 if return_state == EMPTY_RETURN_STATE {
2838 continue;
2839 }
2840 let parent = arena.parent(current, index).expect("non-empty parent");
2841 let parent_id = if let Some(&parent_id) = context_ids.get(&parent) {
2842 parent_id
2843 } else {
2844 let parent_id = context_ids.len();
2845 context_ids.insert(parent, parent_id);
2846 work_list.push_front(parent);
2847 parent_id
2848 };
2849
2850 write!(&mut edges, " s{current_id}").expect("write to string");
2851 if len > 1 {
2852 write!(&mut edges, ":p{index}").expect("write to string");
2853 }
2854 writeln!(&mut edges, "->s{parent_id}[label=\"{return_state}\"];")
2855 .expect("write to string");
2856 }
2857 }
2858
2859 let mut dot = String::from("digraph G {\nrankdir=LR;\n");
2860 dot.push_str(&nodes);
2861 dot.push_str(&edges);
2862 dot.push_str("}\n");
2863 dot
2864 }
2865
2866 #[test]
2867 fn pinned_upstream_test_graph_nodes_matches_dot() {
2868 assert_eq!(CASES.len(), 38, "pinned Java source case inventory drifted");
2869 let source_tests = CASES
2870 .iter()
2871 .map(|case| case.source_test)
2872 .collect::<BTreeSet<_>>();
2873 assert_eq!(
2874 source_tests.len(),
2875 38,
2876 "pinned Java source test names must be unique"
2877 );
2878 let logical_ids = CASES
2879 .iter()
2880 .map(|case| case.logical_id)
2881 .collect::<BTreeSet<_>>();
2882 assert_eq!(
2883 logical_ids.len(),
2884 36,
2885 "pinned upstream logical row inventory drifted"
2886 );
2887
2888 let selector = std::env::var("ANTLR_GRAPH_NODE_CASE").ok();
2889 let selected = CASES
2890 .iter()
2891 .filter(|case| {
2892 selector
2893 .as_deref()
2894 .is_none_or(|logical_id| case.logical_id == logical_id)
2895 })
2896 .collect::<Vec<_>>();
2897 assert!(
2898 !selected.is_empty(),
2899 "ANTLR_GRAPH_NODE_CASE={:?} matched no logical row",
2900 selector.as_deref().unwrap_or_default()
2901 );
2902
2903 let mut mismatches = Vec::new();
2904 for case in &selected {
2905 let actual = run_case(case);
2906 if actual != case.expected {
2907 mismatches.push(format!(
2908 "logical_id={}\nsource_test={}\n--- expected\n{}--- actual\n{}",
2909 case.logical_id, case.source_test, case.expected, actual
2910 ));
2911 }
2912 }
2913
2914 assert!(
2915 mismatches.is_empty(),
2916 "TestGraphNodes DOT mismatches ({}/{} source cases):\n\n{}",
2917 mismatches.len(),
2918 selected.len(),
2919 mismatches.join("\n")
2920 );
2921 }
2922 }
2923}