1use std::collections::VecDeque;
13
14use serde::Deserialize;
15use serde::Serialize;
16
17use crate::ThreadState;
18use crate::slice_boundary::callbacks;
19use crate::slice_boundary::callbacks::Callback;
20use crate::types::LogicalTime;
21use crate::types::Op;
22use crate::types::SchedEvent;
23
24pub(crate) fn recording_enabled(cfg: &crate::Config) -> bool {
27 cfg.chaos
28 && cfg.record_preemptions
29 && !cfg.strict_schedule_boundaries
30 && cfg.replay_schedule_from.is_none()
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
34pub enum Site {
36 Prehook,
38 Posthook,
40 PosthookPmuEpsilon,
42}
43
44#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
45#[serde(deny_unknown_fields)]
46pub struct Origin {
48 pub(crate) callback: Callback,
49 pub(crate) site: Site,
50 pub(crate) progress: u64,
51 pub(crate) last_op: Option<Op>,
52 pub(crate) time: LogicalTime,
53 pub(crate) rcbs: u64,
54}
55
56#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
57#[serde(deny_unknown_fields)]
58pub struct Checkpoint {
60 pub(crate) history_index: usize,
61 pub(crate) origin: Origin,
62}
63
64#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
67pub(crate) struct Token {
68 callback: Option<Callback>,
69 boundary: Option<callbacks::Token>,
70 pre_cut: Option<Progress>,
71 post_cut: Option<Progress>,
72}
73
74impl Token {
75 pub(crate) fn note_cut(&mut self, progress: Progress, site: Site) {
79 match site {
80 Site::Prehook => self.pre_cut = Some(progress),
81 Site::Posthook => self.post_cut = Some(progress),
82 Site::PosthookPmuEpsilon => {}
83 }
84 }
85
86 fn proves_passed(&self, expected: &Origin, actual: &Origin) -> bool {
87 if self.callback != Some(expected.callback) || actual.callback != expected.callback {
88 return false;
89 }
90 let observed = match (expected.site, actual.site) {
91 (Site::Prehook, Site::Posthook | Site::PosthookPmuEpsilon) => &self.pre_cut,
92 (Site::Posthook, Site::PosthookPmuEpsilon) => &self.post_cut,
93 _ => return false,
94 };
95 observed.as_ref().is_some_and(|progress| {
96 progress.count == expected.progress
97 && progress.last_op == expected.last_op
98 && actual.time >= expected.time
99 && actual.rcbs >= expected.rcbs
100 })
101 }
102}
103
104pub(crate) fn begin<T>(
105 thread: &mut ThreadState<T>,
106 callback: Option<Callback>,
107) -> anyhow::Result<Option<Token>> {
108 let boundary = callbacks::begin(thread, callback)?;
109 Ok(Some(Token {
110 callback,
111 boundary,
112 pre_cut: None,
113 post_cut: None,
114 }))
115}
116
117pub(crate) fn pre<T>(thread: &mut ThreadState<T>, token: &mut Option<Token>) -> anyhow::Result<()> {
118 let mut boundary = token.as_mut().and_then(|token| token.boundary.take());
119 let result = callbacks::pre(thread, &mut boundary);
120 if let Some(token) = token {
121 token.boundary = boundary;
122 }
123 result
124}
125
126pub(crate) fn post<T>(thread: &mut ThreadState<T>, token: &Option<Token>) -> anyhow::Result<()> {
127 callbacks::post(
128 thread,
129 token.as_ref().and_then(|token| token.boundary.clone()),
130 )
131}
132
133pub(crate) fn delivered_signal(
134 token: &mut Option<Token>,
135 signal: crate::types::SigWrapper,
136 delivery: u64,
137) -> anyhow::Result<()> {
138 if let Some(token) = token {
139 callbacks::delivered_signal(&mut token.boundary, signal, delivery)?;
140 }
141 Ok(())
142}
143
144#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
146pub struct Progress {
147 pub(crate) count: u64,
148 pub(crate) last_op: Option<Op>,
149}
150
151impl Progress {
152 pub(crate) fn observe_event(&mut self, event: &SchedEvent) {
153 if event.count > 0 {
154 self.count = self
155 .count
156 .checked_add(u64::from(event.count))
157 .expect("callback event progress overflow");
158 self.last_op = Some(event.op);
159 }
160 }
161}
162
163#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
165pub struct Startup {
166 pub(crate) progress: Progress,
167 pub(crate) checkpoints: Vec<Checkpoint>,
168}
169
170#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
171pub(crate) struct State {
172 progress: Progress,
173 checkpoints: VecDeque<Checkpoint>,
174 active: bool,
175}
176
177#[derive(Debug, Eq, PartialEq)]
178pub(crate) enum Decision {
179 Wait,
180 Act(Origin),
181 Mismatch {
182 checkpoint: Checkpoint,
183 hard: bool,
184 actual: Origin,
185 },
186}
187
188#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub(crate) enum ActiveRow {
192 BeforeFirst,
194 Row(usize),
196 Exhausted,
198}
199
200#[derive(Clone, Copy, Debug, Eq, PartialEq)]
204pub(crate) enum Refusal {
205 Skipped,
207 Overshot,
210 Incompatible,
213 Incomplete,
215}
216
217impl Refusal {
218 pub(crate) fn name(self) -> &'static str {
219 match self {
220 Refusal::Skipped => "HERMIT_CALLBACK_CHECKPOINT_SKIPPED",
221 Refusal::Overshot => "HERMIT_CALLBACK_CHECKPOINT_OVERSHOT",
222 Refusal::Incompatible => "HERMIT_CALLBACK_CHECKPOINT_INCOMPATIBLE",
223 Refusal::Incomplete => "HERMIT_CALLBACK_CHECKPOINT_INCOMPLETE",
224 }
225 }
226}
227
228#[derive(Debug, Eq, PartialEq)]
231pub(crate) enum RowDecision {
232 NoCheckpoint,
234 Pending,
236 Match(Checkpoint),
238 Refuse {
241 refusal: Refusal,
242 checkpoint: Checkpoint,
243 actual: Option<Origin>,
244 },
245}
246
247impl State {
248 pub(crate) fn is_active(&self) -> bool {
249 self.active
250 }
251
252 pub(crate) fn progress(&self) -> Progress {
253 self.progress.clone()
254 }
255 pub fn observe_event(&mut self, event: &SchedEvent) {
256 self.progress.observe_event(event);
257 }
258
259 pub(crate) fn release(&mut self) {
264 self.checkpoints.clear();
265 }
266
267 pub fn install(&mut self, startup: Startup) {
268 if self.active || self.progress != Progress::default() {
269 assert_eq!(
270 self.progress, startup.progress,
271 "preserved Tool progress matches actual coordinator observations"
272 );
273 assert_eq!(
274 self.checkpoints.iter().cloned().collect::<Vec<_>>(),
275 startup.checkpoints,
276 "preserved checkpoint custody matches coordinator"
277 );
278 }
279 self.progress = startup.progress;
280 self.checkpoints = startup.checkpoints.into();
281 self.active = true;
284 }
285
286 pub fn origin<T>(
287 &self,
288 thread: &ThreadState<T>,
289 token: &Option<Token>,
290 site: Site,
291 ) -> Option<Origin> {
292 Some(Origin {
293 callback: token.as_ref()?.callback?,
294 site,
295 progress: self.progress.count,
296 last_op: self.progress.last_op,
297 time: thread.thread_logical_time.as_nanos(),
298 rcbs: thread.committed_clock_value,
299 })
300 }
301
302 pub(crate) fn observed<T>(
305 &self,
306 thread: &ThreadState<T>,
307 token: Option<&Token>,
308 site: Site,
309 ) -> Option<Origin> {
310 Some(Origin {
311 callback: token?.callback?,
312 site,
313 progress: self.progress.count,
314 last_op: self.progress.last_op,
315 time: thread.thread_logical_time.as_nanos(),
316 rcbs: thread.committed_clock_value,
317 })
318 }
319
320 pub fn refresh<T>(&self, thread: &ThreadState<T>, origin: &Origin) -> Origin {
321 Origin {
322 callback: origin.callback,
323 site: origin.site,
324 progress: self.progress.count,
325 last_op: self.progress.last_op,
326 time: thread.thread_logical_time.as_nanos(),
327 rcbs: thread.committed_clock_value,
328 }
329 }
330
331 #[cfg(test)]
335 pub fn check(&mut self, actual: Origin) -> Decision {
336 self.check_at(actual, None)
337 }
338
339 pub(crate) fn check_at(&mut self, actual: Origin, token: Option<&Token>) -> Decision {
340 let Some(expected) = self.checkpoints.front() else {
341 return Decision::Wait;
342 };
343 if actual.progress < expected.origin.progress {
344 return Decision::Wait;
345 }
346 if actual.progress == expected.origin.progress {
347 if (actual.callback != expected.origin.callback || actual.site != expected.origin.site)
348 && !token.is_some_and(|token| token.proves_passed(&expected.origin, &actual))
349 {
350 return Decision::Wait;
351 }
352 if actual.time < expected.origin.time || actual.rcbs < expected.origin.rcbs {
353 return Decision::Wait;
354 }
355 }
356 let expected = self.checkpoints.pop_front().unwrap();
357 if actual == expected.origin {
358 return Decision::Act(actual);
359 }
360 let hard = actual.progress != expected.origin.progress
361 || actual.callback != expected.origin.callback
362 || actual.site != expected.origin.site
363 || actual.last_op != expected.origin.last_op;
364 Decision::Mismatch {
365 checkpoint: expected,
366 hard,
367 actual,
368 }
369 }
370
371 pub(crate) fn decide_row(
397 &mut self,
398 active: ActiveRow,
399 actual: Option<Origin>,
400 token: Option<&Token>,
401 rcbs: u64,
402 ) -> RowDecision {
403 let Some(front) = self.checkpoints.front() else {
404 return RowDecision::NoCheckpoint;
405 };
406 let row = match active {
407 ActiveRow::BeforeFirst => return RowDecision::NoCheckpoint,
408 ActiveRow::Exhausted => return self.refuse_front(Refusal::Incomplete, actual),
409 ActiveRow::Row(row) => row,
410 };
411 if front.history_index < row {
412 return self.refuse_front(Refusal::Skipped, actual);
413 }
414 if front.history_index > row {
415 return RowDecision::NoCheckpoint;
416 }
417 let expected = front.origin.clone();
418 let Some(actual) = actual else {
419 if self.progress.count > expected.progress || rcbs > expected.rcbs {
420 return self.refuse_front(Refusal::Overshot, None);
421 }
422 return RowDecision::Pending;
423 };
424 let passed = actual.progress > expected.progress
425 || actual.rcbs > expected.rcbs
426 || (actual.progress == expected.progress
427 && token.is_some_and(|token| token.proves_passed(&expected, &actual)));
428 if passed {
429 return self.refuse_front(Refusal::Overshot, Some(actual));
430 }
431 if actual.progress < expected.progress || actual.rcbs < expected.rcbs {
432 return RowDecision::Pending;
433 }
434 if actual.callback != expected.callback || actual.site != expected.site {
435 return RowDecision::Pending;
436 }
437 if actual.time != expected.time || actual.last_op != expected.last_op {
438 return self.refuse_front(Refusal::Incompatible, Some(actual));
439 }
440 RowDecision::Match(self.checkpoints.pop_front().unwrap())
441 }
442
443 fn refuse_front(&mut self, refusal: Refusal, actual: Option<Origin>) -> RowDecision {
444 RowDecision::Refuse {
445 refusal,
446 checkpoint: self.checkpoints.pop_front().unwrap(),
447 actual,
448 }
449 }
450
451 pub(crate) fn remaining(&self) -> impl Iterator<Item = &Checkpoint> {
453 self.checkpoints.iter()
454 }
455
456 pub(crate) fn annotates(&self, row: usize) -> bool {
458 self.checkpoints
459 .front()
460 .is_some_and(|front| front.history_index == row)
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 use detcore_model::schedule::SyscallPhase;
467 use reverie::syscalls::Sysno;
468
469 use super::*;
470 use crate::config::Config;
471 use crate::types::DetTid;
472
473 fn thread() -> ThreadState<()> {
474 ThreadState::new(DetTid::from_raw(3), &Config::default(), ())
475 }
476 fn origin() -> Origin {
477 Origin {
478 callback: Callback::Syscall(Sysno::close),
479 site: Site::Posthook,
480 progress: 2,
481 last_op: Some(Op::Syscall(Sysno::close, SyscallPhase::Posthook)),
482 time: LogicalTime::from_nanos(100),
483 rcbs: 10,
484 }
485 }
486 fn replay(origin: Origin) -> State {
487 let mut state = State::default();
488 state.install(Startup {
489 checkpoints: vec![Checkpoint {
490 history_index: 0,
491 origin,
492 }],
493 ..Startup::default()
494 });
495 state
496 }
497
498 #[test]
499 fn a_released_replay_drops_the_remaining_checkpoints() {
500 let mut state = replay(origin());
501 state.release();
502 assert_eq!(state.check(origin()), Decision::Wait);
503 assert!(state.is_active());
504 }
505
506 #[test]
507 fn callback_checkpoint_requires_phase_progress_and_both_clocks() {
508 let expected = origin();
509 let mut state = replay(expected.clone());
510 for actual in [
511 Origin {
512 site: Site::Prehook,
513 ..expected.clone()
514 },
515 Origin {
516 time: LogicalTime::from_nanos(75),
517 ..expected.clone()
518 },
519 Origin {
520 rcbs: 9,
521 ..expected.clone()
522 },
523 Origin {
524 progress: 1,
525 ..expected.clone()
526 },
527 Origin {
528 callback: Callback::Cpuid,
529 ..expected.clone()
530 },
531 ] {
532 assert_eq!(state.check(actual), Decision::Wait);
533 assert_eq!(state.checkpoints.len(), 1);
534 }
535 assert_eq!(
536 state.check(expected.clone()),
537 Decision::Act(expected.clone())
538 );
539 assert_eq!(state.check(expected), Decision::Wait);
540 }
541
542 #[test]
543 fn callback_checkpoint_missed_endpoint_is_hard_but_clock_drift_is_soft() {
544 let expected = origin();
545 for (actual, hard) in [
546 (
547 Origin {
548 progress: 3,
549 ..expected.clone()
550 },
551 true,
552 ),
553 (
554 Origin {
555 last_op: Some(Op::OtherInstructions),
556 ..expected.clone()
557 },
558 true,
559 ),
560 (
561 Origin {
562 time: LogicalTime::from_nanos(101),
563 ..expected.clone()
564 },
565 false,
566 ),
567 (
568 Origin {
569 rcbs: 11,
570 ..expected.clone()
571 },
572 false,
573 ),
574 ] {
575 let mut state = replay(expected.clone());
576 assert!(
577 matches!(state.check(actual), Decision::Mismatch { hard: actual_hard, .. } if actual_hard == hard)
578 );
579 assert!(state.checkpoints.is_empty());
580 }
581 }
582
583 #[test]
584 fn callback_checkpoint_counts_positive_events_and_preserves_nested_handler_identity() {
585 let mut whole = State::default();
586 let mut split = State::default();
587 let tid = DetTid::from_raw(3);
588 whole.observe_event(&SchedEvent::branches(tid, 10));
589 for count in [0, 3, 0, 7, 0] {
590 split.observe_event(&SchedEvent::branches(tid, count));
591 }
592 assert_eq!(whole, split);
593 let mut thread = thread();
594 let mut outer = begin(&mut thread, Some(Callback::Syscall(Sysno::read))).unwrap();
595 pre(&mut thread, &mut outer).unwrap();
596 let mut inner = begin(&mut thread, Some(Callback::Syscall(Sysno::getpid))).unwrap();
597 pre(&mut thread, &mut inner).unwrap();
598 post(&mut thread, &inner).unwrap();
599 assert!(thread.slice_boundary.is_none());
600 assert_eq!(
601 whole
602 .origin(&thread, &outer, Site::Posthook)
603 .unwrap()
604 .callback,
605 Callback::Syscall(Sysno::read)
606 );
607 post(&mut thread, &outer).unwrap();
608 let _abandoned = begin(&mut thread, Some(Callback::Cpuid)).unwrap();
609 assert_eq!(
610 whole
611 .origin(&thread, &inner, Site::PosthookPmuEpsilon)
612 .unwrap()
613 .callback,
614 Callback::Syscall(Sysno::getpid)
615 );
616 }
617
618 #[test]
619 fn callback_checkpoint_passed_owned_phase_is_hard_without_fragment_authority() {
620 let expected = Origin {
621 site: Site::Prehook,
622 progress: 10,
623 last_op: Some(Op::Branch),
624 ..origin()
625 };
626 let tid = DetTid::from_raw(3);
627 let mut whole_thread = thread();
628 let mut split_thread = thread();
629 split_thread
630 .callback_history
631 .observe_event(&SchedEvent::branches(tid, 3));
632 let mut whole = begin(&mut whole_thread, Some(expected.callback))
633 .unwrap()
634 .unwrap();
635 let mut split = begin(&mut split_thread, Some(expected.callback))
636 .unwrap()
637 .unwrap();
638 whole_thread
639 .callback_history
640 .observe_event(&SchedEvent::branches(tid, 10));
641 split_thread
642 .callback_history
643 .observe_event(&SchedEvent::branches(tid, 7));
644 whole.note_cut(whole_thread.callback_history.progress(), Site::Prehook);
645 split.note_cut(split_thread.callback_history.progress(), Site::Prehook);
646 assert_eq!(
647 whole, split,
648 "only the actual cut endpoint is retained locally"
649 );
650 let later = Origin {
651 site: Site::Posthook,
652 ..expected.clone()
653 };
654 let nested = begin(&mut split_thread, Some(expected.callback))
655 .unwrap()
656 .unwrap();
657 let mut state = replay(expected.clone());
658 assert_eq!(state.check_at(later.clone(), Some(&nested)), Decision::Wait);
659 assert_eq!(
660 state.check_at(
661 Origin {
662 time: LogicalTime::from_nanos(99),
663 ..later.clone()
664 },
665 Some(&split)
666 ),
667 Decision::Wait
668 );
669 assert!(matches!(
670 state.check_at(later, Some(&split)),
671 Decision::Mismatch { hard: true, .. }
672 ));
673 assert!(state.checkpoints.is_empty());
674 let expected = Origin {
675 site: Site::Posthook,
676 ..expected
677 };
678 whole.note_cut(whole_thread.callback_history.progress(), Site::Posthook);
679 let mut state = replay(expected.clone());
680 assert!(matches!(
681 state.check_at(
682 Origin {
683 site: Site::PosthookPmuEpsilon,
684 ..expected
685 },
686 Some(&whole)
687 ),
688 Decision::Mismatch { hard: true, .. }
689 ));
690 }
691
692 #[test]
693 fn callback_checkpoint_wire_retains_full_positional_payload() {
694 let point = Checkpoint {
695 history_index: 7,
696 origin: origin(),
697 };
698 let response = crate::tool_global::GlobalResponse::WithCallbackHistory(
699 Startup {
700 checkpoints: vec![point.clone()],
701 ..Startup::default()
702 },
703 Box::new(crate::tool_global::GlobalResponse::StartNewThread(None)),
704 );
705 let bytes = bincode::serde::encode_to_vec(&response, bincode::config::standard()).unwrap();
706 let (decoded, used): (crate::tool_global::GlobalResponse, usize) =
707 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
708 assert_eq!(decoded, response);
709 assert_eq!(used, bytes.len());
710 let mut resources = crate::resources::Resources::new(DetTid::from_raw(3));
711 for inline in [None, Some(point.origin.clone())] {
713 let boxed = inline.clone().map(Box::new);
714 assert_eq!(
715 bincode::serde::encode_to_vec(&inline, bincode::config::standard()).unwrap(),
716 bincode::serde::encode_to_vec(&boxed, bincode::config::standard()).unwrap(),
717 );
718 assert_eq!(
719 serde_json::to_vec(&inline).unwrap(),
720 serde_json::to_vec(&boxed).unwrap(),
721 );
722 }
723 resources.callback_checkpoint = Some(Box::new(point.origin));
724 let bytes = bincode::serde::encode_to_vec(&resources, bincode::config::standard()).unwrap();
725 let (decoded, used): (crate::resources::Resources, usize) =
726 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
727 assert_eq!(decoded, resources);
728 assert_eq!(used, bytes.len());
729 }
730
731 #[test]
732 fn callback_history_activation_survives_empty_custody_and_refuses_disagreement() {
733 let expected = Origin {
734 progress: 0,
735 last_op: None,
736 ..origin()
737 };
738 let mut state = replay(expected.clone());
739 assert_eq!(state.check(expected.clone()), Decision::Act(expected));
740 assert!(state.checkpoints.is_empty());
741 assert!(state.is_active());
742 state.observe_event(&SchedEvent::branches(DetTid::from_raw(3), 7));
743 let startup = Startup {
744 progress: state.progress(),
745 checkpoints: Vec::new(),
746 };
747 let mut reloaded = State::default();
748 reloaded.install(startup.clone());
749 assert_eq!(reloaded, state);
750 reloaded.install(startup);
751 assert_eq!(reloaded, state);
752 assert!(
753 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
754 reloaded.install(Startup::default());
755 }))
756 .is_err()
757 );
758
759 let mut empty = State::default();
762 empty.install(Startup::default());
763 assert!(empty.is_active());
764 assert!(
765 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
766 empty.install(Startup {
767 checkpoints: vec![Checkpoint {
768 history_index: 0,
769 origin: origin(),
770 }],
771 ..Startup::default()
772 });
773 }))
774 .is_err()
775 );
776 for original in [State::default(), state] {
777 let bytes =
778 bincode::serde::encode_to_vec(&original, bincode::config::legacy()).unwrap();
779 let (decoded, used): (State, usize) =
780 bincode::serde::decode_from_slice(&bytes, bincode::config::legacy()).unwrap();
781 assert_eq!(decoded, original);
782 assert_eq!(used, bytes.len());
783 }
784 }
785
786 fn rows(points: &[(usize, Origin)]) -> State {
788 let mut state = State::default();
789 state.install(Startup {
790 checkpoints: points
791 .iter()
792 .map(|(history_index, origin)| Checkpoint {
793 history_index: *history_index,
794 origin: origin.clone(),
795 })
796 .collect(),
797 ..Startup::default()
798 });
799 state
800 }
801
802 #[test]
810 fn a_priority_row_ends_only_at_its_recorded_endpoint() {
811 let expected = origin();
812 let mut state = rows(&[(1, expected.clone())]);
813 let endpoint_rcbs = expected.rcbs;
814 let decide = move |state: &mut State, active, actual: Option<Origin>| {
815 let rcbs = actual.as_ref().map_or(endpoint_rcbs, |actual| actual.rcbs);
816 state.decide_row(active, actual, None, rcbs)
817 };
818 assert_eq!(
819 decide(&mut state, ActiveRow::BeforeFirst, Some(expected.clone())),
820 RowDecision::NoCheckpoint
821 );
822 assert_eq!(
823 decide(&mut state, ActiveRow::Row(0), Some(expected.clone())),
824 RowDecision::NoCheckpoint,
825 "row 0 has no checkpoint: the legacy expiry decides it"
826 );
827 for actual in [
828 Origin {
829 progress: 1,
830 ..expected.clone()
831 },
832 Origin {
833 rcbs: 9,
834 ..expected.clone()
835 },
836 Origin {
837 site: Site::Prehook,
838 ..expected.clone()
839 },
840 Origin {
841 callback: Callback::PmuTimer,
842 site: Site::Prehook,
843 ..expected.clone()
844 },
845 ] {
846 assert_eq!(
847 decide(&mut state, ActiveRow::Row(1), Some(actual.clone())),
848 RowDecision::Pending,
849 "{actual:?}"
850 );
851 }
852 assert_eq!(
853 decide(&mut state, ActiveRow::Row(1), None),
854 RowDecision::Pending
855 );
856 assert!(state.annotates(1), "nothing was consumed");
857 assert_eq!(
858 decide(&mut state, ActiveRow::Row(1), Some(expected.clone())),
859 RowDecision::Match(Checkpoint {
860 history_index: 1,
861 origin: expected.clone(),
862 })
863 );
864 assert_eq!(state.remaining().count(), 0);
865 assert_eq!(
866 decide(&mut state, ActiveRow::Exhausted, Some(expected)),
867 RowDecision::NoCheckpoint,
868 "a consumed history ends in the legacy final slice"
869 );
870 }
871
872 #[test]
882 fn the_retained_sapphire_rapids_row_waits_past_its_timer_stop_for_the_write_prehook() {
883 let time = LogicalTime::from_nanos(1_792_852_217_684_087_955);
884 let recorded = Origin {
885 callback: Callback::Syscall(Sysno::write),
886 site: Site::Prehook,
887 progress: 10_759_483,
888 last_op: Some(Op::OtherInstructions),
889 time,
890 rcbs: 10_759_329,
891 };
892 let timer_stop = Origin {
893 callback: Callback::PmuTimer,
894 site: Site::Prehook,
895 progress: 10_759_482,
896 last_op: Some(Op::Branch),
897 time,
898 rcbs: 10_759_329,
899 };
900 let row0_endpoint = Origin {
901 callback: Callback::PmuTimer,
902 site: Site::Prehook,
903 progress: 2_459_633,
904 last_op: Some(Op::Branch),
905 time: LogicalTime::from_nanos(1_792_852_217_600_760_455),
906 rcbs: 2_459_579,
907 };
908 let mut state = rows(&[(0, row0_endpoint.clone()), (1, recorded.clone())]);
909 let RowDecision::Match(row0) =
910 state.decide_row(ActiveRow::Row(0), Some(row0_endpoint), None, 2_459_579)
911 else {
912 panic!("row 0 ends at its recorded timer stop");
913 };
914 assert_eq!(row0.history_index, 0);
915 assert_eq!(
916 state.decide_row(ActiveRow::Row(1), Some(timer_stop), None, 10_759_329),
917 RowDecision::Pending,
918 "the timer stop at the endpoint's RCBs, one event early, must not end row 1"
919 );
920 assert!(state.annotates(1), "row 1's checkpoint was not consumed");
921 assert_eq!(
922 state.decide_row(
923 ActiveRow::Row(1),
924 Some(recorded.clone()),
925 None,
926 recorded.rcbs
927 ),
928 RowDecision::Match(Checkpoint {
929 history_index: 1,
930 origin: recorded,
931 })
932 );
933 assert_eq!(state.remaining().count(), 0);
934 }
935
936 #[test]
943 fn a_priority_row_refuses_every_endpoint_it_cannot_honor() {
944 let expected = origin();
945 for (active, actual, refusal) in [
946 (
947 ActiveRow::Row(1),
948 Origin {
949 progress: 3,
950 ..expected.clone()
951 },
952 Refusal::Overshot,
953 ),
954 (
955 ActiveRow::Row(1),
956 Origin {
957 rcbs: 11,
958 site: Site::Prehook,
959 ..expected.clone()
960 },
961 Refusal::Overshot,
962 ),
963 (
964 ActiveRow::Row(1),
965 Origin {
966 time: LogicalTime::from_nanos(101),
967 ..expected.clone()
968 },
969 Refusal::Incompatible,
970 ),
971 (
972 ActiveRow::Row(1),
973 Origin {
974 last_op: Some(Op::Branch),
975 ..expected.clone()
976 },
977 Refusal::Incompatible,
978 ),
979 (ActiveRow::Row(2), expected.clone(), Refusal::Skipped),
980 (ActiveRow::Exhausted, expected.clone(), Refusal::Incomplete),
981 ] {
982 let mut state = rows(&[(1, expected.clone()), (3, expected.clone())]);
983 assert_eq!(
984 state.decide_row(active, Some(actual.clone()), None, actual.rcbs),
985 RowDecision::Refuse {
986 refusal,
987 checkpoint: Checkpoint {
988 history_index: 1,
989 origin: expected.clone(),
990 },
991 actual: Some(actual.clone()),
992 },
993 "{active:?} {actual:?}"
994 );
995 assert!(
996 state.annotates(3),
997 "only the refused checkpoint is consumed"
998 );
999 }
1000 let mut state = rows(&[(1, expected.clone())]);
1002 assert!(matches!(
1003 state.decide_row(ActiveRow::Row(2), None, None, 0),
1004 RowDecision::Refuse {
1005 refusal: Refusal::Skipped,
1006 actual: None,
1007 ..
1008 }
1009 ));
1010 assert_eq!(
1011 Refusal::Skipped.name(),
1012 "HERMIT_CALLBACK_CHECKPOINT_SKIPPED"
1013 );
1014 assert_eq!(
1015 Refusal::Overshot.name(),
1016 "HERMIT_CALLBACK_CHECKPOINT_OVERSHOT"
1017 );
1018 assert_eq!(
1019 Refusal::Incompatible.name(),
1020 "HERMIT_CALLBACK_CHECKPOINT_INCOMPATIBLE"
1021 );
1022 assert_eq!(
1023 Refusal::Incomplete.name(),
1024 "HERMIT_CALLBACK_CHECKPOINT_INCOMPLETE"
1025 );
1026 }
1027
1028 #[test]
1034 fn a_check_in_without_identity_past_the_endpoint_is_refused_as_overshot() {
1035 let expected = origin();
1036 let tid = DetTid::from_raw(3);
1042 let at_endpoint = |progress: u32| {
1043 let mut state = rows(&[(1, expected.clone()), (3, expected.clone())]);
1044 state.observe_event(&SchedEvent::branches(tid, progress));
1045 state
1046 };
1047 let mut state = at_endpoint(2);
1048 assert_eq!(
1049 state.decide_row(ActiveRow::Row(1), None, None, expected.rcbs),
1050 RowDecision::Pending,
1051 "at the endpoint's progress and RCBs"
1052 );
1053 assert!(state.annotates(1), "a pending check-in consumes nothing");
1054 for (progress, rcbs, why) in [
1055 (2, expected.rcbs + 1, "one RCB past the endpoint"),
1056 (3, expected.rcbs, "one progress unit past the endpoint"),
1057 ] {
1058 let mut state = at_endpoint(progress);
1059 assert_eq!(
1060 state.decide_row(ActiveRow::Row(1), None, None, rcbs),
1061 RowDecision::Refuse {
1062 refusal: Refusal::Overshot,
1063 checkpoint: Checkpoint {
1064 history_index: 1,
1065 origin: expected.clone(),
1066 },
1067 actual: None,
1068 },
1069 "{why}"
1070 );
1071 assert!(
1072 state.annotates(3),
1073 "{why}: only the refused checkpoint is consumed"
1074 );
1075 }
1076 for (active, refusal) in [
1079 (ActiveRow::Row(2), Refusal::Skipped),
1080 (ActiveRow::Exhausted, Refusal::Incomplete),
1081 ] {
1082 let mut state = at_endpoint(3);
1083 assert!(
1084 matches!(
1085 state.decide_row(active, None, None, expected.rcbs + 1),
1086 RowDecision::Refuse { refusal: r, actual: None, .. } if r == refusal
1087 ),
1088 "{active:?}"
1089 );
1090 }
1091 }
1092
1093 #[test]
1098 fn a_priority_row_refuses_a_prehook_endpoint_its_own_callback_left() {
1099 let tid = DetTid::from_raw(3);
1100 let expected = Origin {
1101 site: Site::Prehook,
1102 progress: 2,
1103 last_op: Some(Op::Branch),
1104 ..origin()
1105 };
1106 let later = Origin {
1107 site: Site::Posthook,
1108 ..expected.clone()
1109 };
1110 let mut thread = thread();
1111 let mut token = begin(&mut thread, Some(expected.callback))
1112 .unwrap()
1113 .unwrap();
1114 let unrelated = begin(&mut thread, Some(Callback::Cpuid)).unwrap().unwrap();
1115 let uncut = token.clone();
1116 thread
1117 .callback_history
1118 .observe_event(&SchedEvent::branches(tid, 2));
1119 token.note_cut(thread.callback_history.progress(), Site::Prehook);
1120 for witness in [&unrelated, &uncut] {
1121 let mut state = rows(&[(0, expected.clone())]);
1122 assert_eq!(
1123 state.decide_row(
1124 ActiveRow::Row(0),
1125 Some(later.clone()),
1126 Some(witness),
1127 later.rcbs
1128 ),
1129 RowDecision::Pending
1130 );
1131 }
1132 let mut state = rows(&[(0, expected.clone())]);
1133 assert!(matches!(
1134 state.decide_row(
1135 ActiveRow::Row(0),
1136 Some(later.clone()),
1137 Some(&token),
1138 later.rcbs
1139 ),
1140 RowDecision::Refuse {
1141 refusal: Refusal::Overshot,
1142 ..
1143 }
1144 ));
1145 }
1146
1147 #[test]
1152 fn the_active_priority_row_follows_the_history_cursor() {
1153 use crate::preemptions::ThreadHistory;
1154 let mut thread = thread();
1155 assert_eq!(thread.active_priority_row(), None, "no history replayed");
1156 thread.preemption_points = Some(
1157 ThreadHistory::new()
1158 .with_prio_changes(vec![
1159 (LogicalTime::from_nanos(10), 7),
1160 (LogicalTime::from_nanos(20), 8),
1161 ])
1162 .with_preemption_rcbs(vec![10, 20])
1163 .into_iter(),
1164 );
1165 thread.callback_history = rows(&[(1, origin())]);
1166 assert_eq!(thread.active_priority_row(), Some(ActiveRow::BeforeFirst));
1167 assert!(thread.pending_checkpoint().is_none());
1168 let history = thread.preemption_points.as_mut().unwrap();
1169 history.next_with_rcbs().unwrap();
1170 assert_eq!(thread.active_priority_row(), Some(ActiveRow::Row(0)));
1171 assert!(
1172 thread.pending_checkpoint().is_none(),
1173 "row 0 is unannotated"
1174 );
1175 thread
1176 .preemption_points
1177 .as_mut()
1178 .unwrap()
1179 .next_with_rcbs()
1180 .unwrap();
1181 assert_eq!(thread.active_priority_row(), Some(ActiveRow::Row(1)));
1182 assert_eq!(thread.pending_checkpoint().unwrap().origin, origin());
1183 assert!(
1184 thread
1185 .preemption_points
1186 .as_mut()
1187 .unwrap()
1188 .next_with_rcbs()
1189 .is_none()
1190 );
1191 thread.stats.last_recorded_slice = Some(thread.stats.timeslice_count);
1192 assert_eq!(thread.active_priority_row(), Some(ActiveRow::Exhausted));
1193 assert!(thread.pending_checkpoint().is_none());
1194 }
1195
1196 #[test]
1199 fn an_observed_priority_checkin_reads_actual_coordinates() {
1200 let tid = DetTid::from_raw(3);
1201 let mut thread = thread();
1202 thread.committed_clock_value = 41;
1203 thread
1204 .callback_history
1205 .observe_event(&SchedEvent::branches(tid, 5));
1206 let token = begin(&mut thread, Some(Callback::Rdtsc)).unwrap().unwrap();
1207 let observed = thread
1208 .callback_history
1209 .observed(&thread, Some(&token), Site::Prehook)
1210 .unwrap();
1211 assert_eq!(observed.callback, Callback::Rdtsc);
1212 assert_eq!(observed.site, Site::Prehook);
1213 assert_eq!(observed.progress, 5);
1214 assert_eq!(observed.last_op, Some(Op::Branch));
1215 assert_eq!(observed.rcbs, 41);
1216 assert_eq!(observed.time, thread.thread_logical_time.as_nanos());
1217 assert_eq!(
1218 thread
1219 .callback_history
1220 .observed(&thread, None, Site::Prehook),
1221 None
1222 );
1223 let anonymous = begin(&mut thread, None).unwrap().unwrap();
1224 assert_eq!(
1225 thread
1226 .callback_history
1227 .observed(&thread, Some(&anonymous), Site::Prehook),
1228 None
1229 );
1230 }
1231}