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