1use std::{
18 collections::{BTreeMap, HashMap},
19 fmt::Debug,
20};
21
22#[cfg(not(feature = "python"))]
23use optipy::strip_pyo3;
24#[cfg(feature = "stubs")]
25use pyo3_stub_gen::derive::gen_stub_pyclass;
26
27use crate::{
28 instruction::{
29 Instruction, InstructionHandler, Jump, JumpUnless, JumpWhen, Label, MemoryReference, Target,
30 },
31 program::{
32 scheduling::{
33 schedule::{ComputedScheduleError, ComputedScheduleItem, Schedule, TimeSpan, Zero},
34 ScheduleError, ScheduledBasicBlock, Seconds,
35 },
36 ProgramError,
37 },
38 Program,
39};
40
41#[derive(Clone, Debug, Default)]
45pub struct ControlFlowGraph<'p> {
46 blocks: Vec<BasicBlock<'p>>,
47}
48
49impl<'p> ControlFlowGraph<'p> {
50 pub fn has_dynamic_control_flow(&self) -> bool {
52 self.blocks
53 .iter()
54 .any(|block| block.terminator().is_dynamic())
55 }
56
57 pub fn into_blocks(self) -> Vec<BasicBlock<'p>> {
59 self.blocks
60 }
61}
62
63#[derive(Clone, Debug)]
64#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
65#[cfg_attr(
66 feature = "python",
67 pyo3::pyclass(name = "ControlFlowGraph", module = "quil.program", subclass, frozen)
68)]
69pub struct ControlFlowGraphOwned {
70 pub(crate) blocks: Vec<BasicBlockOwned>,
71}
72
73impl From<ControlFlowGraph<'_>> for ControlFlowGraphOwned {
74 fn from(value: ControlFlowGraph) -> Self {
75 let blocks = value
76 .blocks
77 .into_iter()
78 .map(BasicBlockOwned::from)
79 .collect();
80 ControlFlowGraphOwned { blocks }
81 }
82}
83
84impl<'p> From<&'p ControlFlowGraphOwned> for ControlFlowGraph<'p> {
85 fn from(value: &'p ControlFlowGraphOwned) -> Self {
86 let blocks = value.blocks.iter().map(BasicBlock::from).collect();
87 ControlFlowGraph { blocks }
88 }
89}
90
91#[derive(Clone, Debug, Default)]
92pub struct BasicBlock<'p> {
93 label: Option<&'p Target>,
96
97 instructions: Vec<&'p Instruction>,
99
100 instruction_index_offset: usize,
106
107 terminator: BasicBlockTerminator<'p>,
109}
110
111impl<'p> BasicBlock<'p> {
112 pub fn label(&self) -> Option<&'p Target> {
113 self.label
114 }
115
116 pub fn instruction_index_offset(&self) -> usize {
117 self.instruction_index_offset
118 }
119
120 pub fn instructions(&self) -> &[&'p Instruction] {
121 self.instructions.as_ref()
122 }
123
124 pub fn terminator(&self) -> &BasicBlockTerminator<'p> {
125 &self.terminator
126 }
127
128 pub fn as_schedule_seconds(
178 &self,
179 program: &Program,
180 ) -> Result<Schedule<Seconds>, BasicBlockScheduleError> {
181 self.as_schedule(
182 program,
183 ScheduledBasicBlock::get_instruction_duration_seconds,
184 )
185 }
186
187 pub fn as_schedule<F, Time>(
227 &self,
228 program: &'p Program,
229 get_duration: F,
230 ) -> Result<Schedule<Time>, BasicBlockScheduleError>
231 where
232 F: Fn(&Program, &Instruction) -> Option<Time>,
233 Time: Clone
234 + Debug
235 + PartialOrd
236 + std::ops::Add<Time, Output = Time>
237 + std::ops::Sub<Time, Output = Time>
238 + Zero,
239 {
240 let mut calibrated_to_uncalibrated_instruction_source_mapping = BTreeMap::new();
242 let mut calibrated_block_instructions = Vec::new();
243
244 for (uncalibrated_instruction_index, instruction) in self.instructions.iter().enumerate() {
245 let first_calibrated_instruction_index = calibrated_block_instructions.len();
246 if let Some(expanded) = program.calibrations.expand(instruction, &[])? {
247 calibrated_block_instructions.extend(expanded.into_iter());
248 } else {
249 calibrated_block_instructions.push((*instruction).clone());
250 }
251 calibrated_to_uncalibrated_instruction_source_mapping.insert(
252 first_calibrated_instruction_index,
253 uncalibrated_instruction_index,
254 );
255 }
256
257 let calibrated_block = BasicBlock {
258 label: self.label,
259 instructions: calibrated_block_instructions.iter().collect(),
260 instruction_index_offset: self.instruction_index_offset,
261 terminator: self.terminator.clone(),
262 };
263
264 let mut instruction_handler = InstructionHandler::default();
266 let scheduled_self =
267 ScheduledBasicBlock::build(calibrated_block, program, &mut instruction_handler)?;
268 let schedule = scheduled_self.as_schedule(program, get_duration)?;
269
270 let uncalibrated_schedule_items_by_instruction_index = schedule
272 .into_items()
273 .into_iter()
274 .fold(HashMap::<usize, TimeSpan<Time>>::new(), |mut map, item| {
275 if let Some((_, uncalibrated_instruction_index)) =
276 calibrated_to_uncalibrated_instruction_source_mapping
277 .range(..=item.instruction_index)
278 .next_back()
279 {
280 if let Some(existing_time_span) = map.get_mut(uncalibrated_instruction_index) {
281 *existing_time_span = existing_time_span.clone().union(item.time_span);
282 } else {
283 map.insert(*uncalibrated_instruction_index, item.time_span.clone());
284 }
285 }
286
287 map
288 });
289
290 let schedule_items = uncalibrated_schedule_items_by_instruction_index
291 .into_iter()
292 .map(|(instruction_index, time_span)| ComputedScheduleItem {
293 instruction_index,
294 time_span,
295 })
296 .collect::<Vec<_>>();
297
298 let schedule = Schedule::from(schedule_items);
299 Ok(schedule)
300 }
301}
302
303#[allow(clippy::large_enum_variant)]
305#[allow(clippy::enum_variant_names)]
306#[derive(Debug, thiserror::Error)]
307pub enum BasicBlockScheduleError {
308 #[error(transparent)]
309 ScheduleError(#[from] ScheduleError),
310
311 #[error(transparent)]
312 ComputedScheduleError(#[from] ComputedScheduleError),
313
314 #[error(transparent)]
315 ProgramError(#[from] ProgramError),
316}
317
318#[derive(Clone, Debug)]
322#[cfg_attr(feature = "stubs", gen_stub_pyclass)]
323#[cfg_attr(
324 feature = "python",
325 pyo3::pyclass(name = "BasicBlock", module = "quil.program", subclass)
326)]
327#[cfg_attr(not(feature = "python"), strip_pyo3)]
328pub struct BasicBlockOwned {
329 #[pyo3(get)]
332 label: Option<Target>,
333 #[pyo3(get)]
337 instructions: Vec<Instruction>,
338 instruction_index_offset: usize,
339 pub(crate) terminator: BasicBlockTerminatorOwned,
340}
341
342impl From<BasicBlock<'_>> for BasicBlockOwned {
343 fn from(value: BasicBlock) -> Self {
344 let label = value.label.cloned();
345 let instructions = value.instructions.into_iter().cloned().collect();
346 let instruction_index_offset = value.instruction_index_offset;
347 let terminator = value.terminator.into();
348 BasicBlockOwned {
349 label,
350 instructions,
351 instruction_index_offset,
352 terminator,
353 }
354 }
355}
356
357impl<'b> From<&'b BasicBlockOwned> for BasicBlock<'b> {
358 fn from(value: &'b BasicBlockOwned) -> Self {
359 let label = value.label.as_ref();
360 let instructions = value.instructions.iter().collect();
361 let instruction_index_offset = value.instruction_index_offset;
362 let terminator = (&value.terminator).into();
363 BasicBlock {
364 label,
365 instructions,
366 instruction_index_offset,
367 terminator,
368 }
369 }
370}
371
372#[derive(Clone, Debug, Default)]
374pub enum BasicBlockTerminator<'p> {
375 ConditionalJump {
376 condition: &'p MemoryReference,
377 target: &'p Target,
378 jump_if_condition_zero: bool,
379 },
380 #[default]
381 Continue,
382 Jump {
383 target: &'p Target,
384 },
385 Halt,
386}
387
388impl BasicBlockTerminator<'_> {
389 pub fn is_dynamic(&self) -> bool {
394 matches!(self, BasicBlockTerminator::ConditionalJump { .. })
395 }
396
397 pub fn into_instruction(self) -> Option<Instruction> {
398 match self {
399 BasicBlockTerminator::ConditionalJump {
400 condition,
401 target,
402 jump_if_condition_zero,
403 } => {
404 if jump_if_condition_zero {
405 Some(Instruction::JumpUnless(JumpUnless {
406 condition: condition.clone(),
407 target: target.clone(),
408 }))
409 } else {
410 Some(Instruction::JumpWhen(JumpWhen {
411 condition: condition.clone(),
412 target: target.clone(),
413 }))
414 }
415 }
416 BasicBlockTerminator::Continue => None,
417 BasicBlockTerminator::Jump { target } => Some(Instruction::Jump(Jump {
418 target: target.clone(),
419 })),
420 BasicBlockTerminator::Halt => Some(Instruction::Halt()),
421 }
422 }
423}
424
425#[derive(Clone, Debug, Default)]
426pub enum BasicBlockTerminatorOwned {
427 ConditionalJump {
428 condition: MemoryReference,
429 target: Target,
430 jump_if_condition_zero: bool,
431 },
432 #[default]
433 Continue,
434 Jump {
435 target: Target,
436 },
437 Halt,
438}
439
440impl From<BasicBlockTerminator<'_>> for BasicBlockTerminatorOwned {
441 fn from(value: BasicBlockTerminator) -> Self {
442 match value {
443 BasicBlockTerminator::ConditionalJump {
444 condition,
445 target,
446 jump_if_condition_zero,
447 } => BasicBlockTerminatorOwned::ConditionalJump {
448 condition: condition.clone(),
449 target: target.clone(),
450 jump_if_condition_zero,
451 },
452 BasicBlockTerminator::Continue => BasicBlockTerminatorOwned::Continue,
453 BasicBlockTerminator::Jump { target } => BasicBlockTerminatorOwned::Jump {
454 target: target.clone(),
455 },
456 BasicBlockTerminator::Halt => BasicBlockTerminatorOwned::Halt,
457 }
458 }
459}
460
461impl<'p> From<&'p BasicBlockTerminatorOwned> for BasicBlockTerminator<'p> {
462 fn from(value: &'p BasicBlockTerminatorOwned) -> Self {
463 match value {
464 BasicBlockTerminatorOwned::ConditionalJump {
465 condition,
466 target,
467 jump_if_condition_zero,
468 } => BasicBlockTerminator::ConditionalJump {
469 condition,
470 target,
471 jump_if_condition_zero: *jump_if_condition_zero,
472 },
473 BasicBlockTerminatorOwned::Continue => BasicBlockTerminator::Continue,
474 BasicBlockTerminatorOwned::Jump { target } => BasicBlockTerminator::Jump { target },
475 BasicBlockTerminatorOwned::Halt => BasicBlockTerminator::Halt,
476 }
477 }
478}
479
480impl<'p> From<&'p Program> for ControlFlowGraph<'p> {
481 fn from(value: &'p Program) -> Self {
482 let mut graph = ControlFlowGraph::default();
483
484 let mut current_label = None;
485 let mut current_block_instructions = Vec::new();
486 let mut instruction_index_offset = 0;
487 for instruction in &value.instructions {
488 match instruction {
489 Instruction::Arithmetic(_)
490 | Instruction::BinaryLogic(_)
491 | Instruction::Call(_)
492 | Instruction::Capture(_)
493 | Instruction::Convert(_)
494 | Instruction::Comparison(_)
495 | Instruction::Delay(_)
496 | Instruction::Fence(_)
497 | Instruction::Exchange(_)
498 | Instruction::Gate(_)
499 | Instruction::Load(_)
500 | Instruction::Pragma(_)
501 | Instruction::Measurement(_)
502 | Instruction::Move(_)
503 | Instruction::Nop()
504 | Instruction::Pulse(_)
505 | Instruction::RawCapture(_)
506 | Instruction::Reset(_)
507 | Instruction::SetFrequency(_)
508 | Instruction::SetPhase(_)
509 | Instruction::SetScale(_)
510 | Instruction::ShiftFrequency(_)
511 | Instruction::ShiftPhase(_)
512 | Instruction::Store(_)
513 | Instruction::SwapPhases(_)
514 | Instruction::UnaryLogic(_)
515 | Instruction::Wait() => current_block_instructions.push(instruction),
516
517 Instruction::CalibrationDefinition(_)
518 | Instruction::CircuitDefinition(_)
519 | Instruction::Declaration(_)
520 | Instruction::FrameDefinition(_)
521 | Instruction::GateDefinition(_)
522 | Instruction::Include(_)
523 | Instruction::MeasureCalibrationDefinition(_)
524 | Instruction::WaveformDefinition(_) => {}
525
526 Instruction::Label(Label { target }) => {
527 if !current_block_instructions.is_empty() || current_label.is_some() {
528 let block = BasicBlock {
529 label: current_label.take(),
530 instructions: std::mem::take(&mut current_block_instructions),
531 instruction_index_offset,
532 terminator: BasicBlockTerminator::Continue,
533 };
534 instruction_index_offset += block.instructions.len() + 1;
536 graph.blocks.push(block);
537 }
538
539 current_label = Some(target);
540 }
541
542 Instruction::Jump(_)
543 | Instruction::JumpUnless(_)
544 | Instruction::JumpWhen(_)
545 | Instruction::Halt() => {
546 let terminator = match instruction {
547 Instruction::Jump(jump) => BasicBlockTerminator::Jump {
548 target: &jump.target,
549 },
550 Instruction::JumpUnless(jump_unless) => {
551 BasicBlockTerminator::ConditionalJump {
552 condition: &jump_unless.condition,
553 target: &jump_unless.target,
554 jump_if_condition_zero: true,
555 }
556 }
557 Instruction::JumpWhen(jump_when) => BasicBlockTerminator::ConditionalJump {
558 condition: &jump_when.condition,
559 target: &jump_when.target,
560 jump_if_condition_zero: false,
561 },
562 Instruction::Halt() => BasicBlockTerminator::Halt,
563 _ => unreachable!(),
564 };
565 let block = BasicBlock {
566 label: current_label.take(),
567 instructions: std::mem::take(&mut current_block_instructions),
568 instruction_index_offset,
569 terminator,
570 };
571
572 let label_instruction_offset = if block.label().is_some() { 1 } else { 0 };
573 instruction_index_offset +=
575 block.instructions.len() + 1 + label_instruction_offset;
576
577 graph.blocks.push(block);
578 }
579 }
580 }
581
582 if !current_block_instructions.is_empty() || current_label.is_some() {
583 let block = BasicBlock {
584 label: current_label.take(),
585 instructions: current_block_instructions,
586 instruction_index_offset,
587 terminator: BasicBlockTerminator::Continue,
588 };
589 graph.blocks.push(block);
590 }
591
592 graph
593 }
594}
595
596impl<'a> TryFrom<&'a Program> for BasicBlock<'a> {
597 type Error = ProgramEmptyOrContainsMultipleBasicBlocks;
598
599 fn try_from(value: &'a Program) -> Result<Self, Self::Error> {
600 let blocks = ControlFlowGraph::from(value).blocks;
601 if blocks.len() == 1 {
602 Ok(blocks.into_iter().next().unwrap())
603 } else {
604 Err(ProgramEmptyOrContainsMultipleBasicBlocks)
605 }
606 }
607}
608
609#[derive(Debug, thiserror::Error)]
610#[error("Program is empty or contains multiple basic blocks")]
611pub struct ProgramEmptyOrContainsMultipleBasicBlocks;
612
613#[cfg(test)]
614mod tests {
615 use crate::Program;
616 use rstest::rstest;
617
618 use super::*;
619
620 use super::super::test_programs::*;
621
622 #[rstest]
623 #[case(QUIL_AS_TREE)]
624 #[case(QUIL_AS_INVERSE_TREE)]
625 #[case(QUIL_AS_LINEAR)]
626 #[case(QUIL_WITH_DIAMOND)]
627 #[case(QUIL_WITH_SWAP)]
628 #[case(KITCHEN_SINK_QUIL)]
629 fn expect_single_basic_block(#[case] input: &str) {
630 let program: Program = input.parse().unwrap();
631 let _: BasicBlock = (&program).try_into().unwrap();
632 }
633
634 #[rstest]
635 #[case(QUIL_AS_TREE, false)]
636 #[case(QUIL_AS_INVERSE_TREE, false)]
637 #[case(QUIL_AS_LINEAR, false)]
638 #[case(QUIL_WITH_DIAMOND, false)]
639 #[case(KITCHEN_SINK_QUIL, false)]
640 #[case(QUIL_WITH_JUMP, false)]
641 #[case(QUIL_WITH_JUMP_WHEN, true)]
642 #[case(QUIL_WITH_JUMP_UNLESS, true)]
643 fn has_dynamic_control_flow(#[case] input: &str, #[case] expected: bool) {
644 let program: Program = input.parse().unwrap();
645 let graph = ControlFlowGraph::from(&program);
646 let dynamic = graph.has_dynamic_control_flow();
647 assert_eq!(expected, dynamic);
648 }
649
650 #[rstest]
651 #[case(r#"LABEL @a
652JUMP @a
653LABEL @b
654JUMP @b
655LABEL @c
656JUMP @c
657"#, vec![0, 2, 4])]
658 #[case(r#"LABEL @a
659LABEL @b
660LABEL @c
661JUMP @c
662"#, vec![0, 1, 2])]
663 #[case(r#"DEFFRAME 0 "rf":
664 ATTRIBUTE: 1
665DEFCAL X 0:
666 Y 0
667X 0
668"#, vec![0])]
669 fn instruction_index_offset(#[case] input: &str, #[case] expected_block_offsets: Vec<usize>) {
670 let program: Program = input.parse().unwrap();
671 let graph = ControlFlowGraph::from(&program);
672 let blocks = graph.into_blocks();
673 println!("blocks: {blocks:#?}");
674 let actual = blocks
675 .iter()
676 .map(|block| block.instruction_index_offset())
677 .collect::<Vec<_>>();
678
679 assert_eq!(expected_block_offsets, actual);
680 }
681
682 #[test]
683 fn schedule_uncalibrated() {
684 let input = r#"DEFFRAME 0 "a":
685 ATTRIBUTE: 1
686
687DEFFRAME 0 "b":
688 ATTRIBUTE: 1
689
690DEFCAL A 0:
691 NONBLOCKING PULSE 0 "a" flat(duration: 1.0)
692 NONBLOCKING PULSE 0 "a" flat(duration: 1.0)
693 NONBLOCKING PULSE 0 "a" flat(duration: 1.0)
694
695DEFCAL B 0:
696 NONBLOCKING PULSE 0 "b" flat(duration: 10.0)
697
698A 0
699B 0
700FENCE
701B 0
702PULSE 0 "a" flat(duration: 1.0)
703"#;
704
705 let program: Program = input.parse().unwrap();
706 let graph = ControlFlowGraph::from(&program);
707 let blocks = graph.into_blocks();
708 println!("blocks: {blocks:#?}");
709
710 let schedule = blocks[0].as_schedule_seconds(&program).unwrap();
711 println!("schedule: {schedule:#?}");
712 assert_eq!(schedule.duration().0, 21.0);
713 let schedule_items = schedule.into_items();
714
715 let schedule_items_by_instruction_index = schedule_items
716 .iter()
717 .map(|item| (item.instruction_index, item.clone()))
718 .collect::<HashMap<_, _>>();
719
720 assert_eq!(
722 schedule_items_by_instruction_index[&0]
723 .time_span
724 .start_time()
725 .0,
726 0.0
727 );
728 assert_eq!(
729 schedule_items_by_instruction_index[&0]
730 .time_span
731 .duration()
732 .0,
733 3.0
734 );
735
736 assert_eq!(
738 schedule_items_by_instruction_index[&1]
739 .time_span
740 .start_time()
741 .0,
742 0.0
743 );
744 assert_eq!(
745 schedule_items_by_instruction_index[&1]
746 .time_span
747 .duration()
748 .0,
749 10.0
750 );
751
752 assert_eq!(
757 schedule_items_by_instruction_index[&3]
758 .time_span
759 .start_time()
760 .0,
761 10.0
762 );
763 assert_eq!(
764 schedule_items_by_instruction_index[&3]
765 .time_span
766 .duration()
767 .0,
768 10.0
769 );
770
771 assert_eq!(
773 schedule_items_by_instruction_index[&4]
774 .time_span
775 .start_time()
776 .0,
777 20.0
778 );
779 assert_eq!(
780 schedule_items_by_instruction_index[&4]
781 .time_span
782 .duration()
783 .0,
784 1.0
785 );
786 }
787
788 #[test]
789 fn schedule_uncalibrated_cz() {
790 let input = r#"DEFFRAME 0 "flux_tx_cz":
791 TEST: 1
792
793DEFFRAME 1 "flux_tx_iswap":
794 TEST: 1
795
796DEFFRAME 1 "flux_tx_cz":
797 TEST: 1
798
799DEFFRAME 1 "flux_tx_iswap":
800 TEST: 1
801
802DEFFRAME 2 "flux_tx_cz":
803 TEST: 1
804
805DEFFRAME 2 "flux_tx_iswap":
806 TEST: 1
807
808DEFFRAME 3 "flux_tx_cz":
809 TEST: 1
810
811DEFFRAME 3 "flux_tx_iswap":
812 TEST: 1
813
814# Simplified version
815DEFCAL CZ q0 q1:
816 FENCE q0 q1
817 SET-PHASE q0 "flux_tx_cz" 0.0
818 SET-PHASE q1 "flux_tx_iswap" 0.0
819 NONBLOCKING PULSE q0 "flux_tx_cz" erf_square(duration: 6.000000000000001e-08, pad_left: 0, pad_right: 0)
820 NONBLOCKING PULSE q1 "flux_tx_iswap" erf_square(duration: 6.000000000000001e-08, pad_left: 0, pad_right: 0)
821 SHIFT-PHASE q0 "flux_tx_cz" 1.0
822 SHIFT-PHASE q1 "flux_tx_iswap" 1.0
823 FENCE q0 q1
824
825CZ 0 1
826CZ 2 3
827CZ 0 2
828"#;
829
830 let program: Program = input.parse().unwrap();
831 let graph = ControlFlowGraph::from(&program);
832 let blocks = graph.into_blocks();
833 println!("blocks: {blocks:#?}");
834
835 let schedule = blocks[0].as_schedule_seconds(&program).unwrap();
836 let mut schedule_items = schedule.into_items();
837 schedule_items.sort_by_key(|item| item.instruction_index);
838
839 assert_eq!(schedule_items.len(), 3);
840
841 insta::assert_debug_snapshot!(schedule_items);
842 }
843}