Repository: rigetti/quil-rs (language: python)

Issue:
feat!: #334: program stats
Closes #334
feat!: #334: program stats
Closes #334
feat!: #334: program stats
Closes #334


Discussion / hints:

Fix patch:
diff --git a/quil-rs/benches/scheduled_program_from_program.rs b/quil-rs/benches/scheduled_program_from_program.rs
index 5e722c3d..ced3c17f 100644
--- a/quil-rs/benches/scheduled_program_from_program.rs
+++ b/quil-rs/benches/scheduled_program_from_program.rs
@@ -1,5 +1,5 @@
 use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
-use quil_rs::{instruction::InstructionHandler, program::graph::ScheduledProgram};
+use quil_rs::{instruction::InstructionHandler, program::scheduling::ScheduledProgram};
 use std::str::FromStr;
 
 mod corpus;
diff --git a/quil-rs/src/lib.rs b/quil-rs/src/lib.rs
index 0777affe..5694b506 100644
--- a/quil-rs/src/lib.rs
+++ b/quil-rs/src/lib.rs
@@ -43,7 +43,6 @@ pub(crate) mod parser;
 pub mod program;
 pub mod quil;
 pub mod reserved;
-pub mod stats;
 pub mod validation;
 
 pub use program::Program;
diff --git a/quil-rs/src/program/analysis/control_flow_graph.rs b/quil-rs/src/program/analysis/control_flow_graph.rs
new file mode 100644
index 00000000..8018813b
--- /dev/null
+++ b/quil-rs/src/program/analysis/control_flow_graph.rs
@@ -0,0 +1,153 @@
+use crate::{
+    instruction::{Instruction, Jump, JumpUnless, JumpWhen, Label, Target},
+    Program,
+};
+
+#[derive(Clone, Debug, Default)]
+pub struct ControlFlowGraph<'p> {
+    blocks: Vec<BasicBlock<'p>>,
+}
+
+impl<'p> ControlFlowGraph<'p> {
+    pub fn into_blocks(self) -> Vec<BasicBlock<'p>> {
+        self.blocks
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct BasicBlock<'p> {
+    label: Option<&'p Target>,
+    instructions: Vec<&'p Instruction>,
+    terminator: BasicBlockTerminator<'p>,
+}
+
+impl<'p> BasicBlock<'p> {
+    pub fn label(&self) -> Option<&Target> {
+        self.label
+    }
+
+    pub fn instructions(&self) -> &[&Instruction] {
+        self.instructions.as_ref()
+    }
+
+    pub fn terminator(&self) -> &BasicBlockTerminator<'p> {
+        &self.terminator
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub enum BasicBlockTerminator<'p> {
+    #[default]
+    Continue,
+    Jump(&'p Jump),
+    JumpWhen(&'p JumpWhen),
+    JumpUnless(&'p JumpUnless),
+    Halt,
+}
+
+impl Program {
+    pub fn as_control_flow_graph(&self) -> ControlFlowGraph {
+        let mut graph = ControlFlowGraph::default();
+
+        let mut current_label = None;
+        let mut current_block_instructions = Vec::new();
+        for instruction in &self.instructions {
+            match instruction {
+                Instruction::Arithmetic(_)
+                | Instruction::BinaryLogic(_)
+                | Instruction::Capture(_)
+                | Instruction::Convert(_)
+                | Instruction::Comparison(_)
+                | Instruction::Delay(_)
+                | Instruction::Fence(_)
+                | Instruction::Exchange(_)
+                | Instruction::Gate(_)
+                | Instruction::Load(_)
+                | Instruction::Pragma(_)
+                | Instruction::Measurement(_)
+                | Instruction::Move(_)
+                | Instruction::Nop
+                | Instruction::Pulse(_)
+                | Instruction::RawCapture(_)
+                | Instruction::Reset(_)
+                | Instruction::SetFrequency(_)
+                | Instruction::SetPhase(_)
+                | Instruction::SetScale(_)
+                | Instruction::ShiftFrequency(_)
+                | Instruction::ShiftPhase(_)
+                | Instruction::Store(_)
+                | Instruction::SwapPhases(_)
+                | Instruction::UnaryLogic(_)
+                | Instruction::Wait => current_block_instructions.push(instruction),
+
+                Instruction::CalibrationDefinition(_)
+                | Instruction::CircuitDefinition(_)
+                | Instruction::Declaration(_)
+                | Instruction::FrameDefinition(_)
+                | Instruction::GateDefinition(_)
+                | Instruction::Include(_)
+                | Instruction::MeasureCalibrationDefinition(_)
+                | Instruction::WaveformDefinition(_) => {}
+
+                Instruction::Jump(_)
+                | Instruction::JumpUnless(_)
+                | Instruction::JumpWhen(_)
+                | Instruction::Label(_)
+                | Instruction::Halt => {
+                    let (terminator, new_label) = match instruction {
+                        Instruction::Jump(jump) => (BasicBlockTerminator::Jump(jump), None),
+                        Instruction::JumpUnless(jump_unless) => {
+                            (BasicBlockTerminator::JumpUnless(jump_unless), None)
+                        }
+                        Instruction::JumpWhen(jump_when) => {
+                            (BasicBlockTerminator::JumpWhen(jump_when), None)
+                        }
+                        Instruction::Label(Label { target }) => {
+                            (BasicBlockTerminator::Continue, Some(target))
+                        }
+                        Instruction::Halt => (BasicBlockTerminator::Halt, None),
+                        _ => unreachable!(),
+                    };
+                    if !current_block_instructions.is_empty() {
+                        let block = BasicBlock {
+                            label: current_label.take(),
+                            instructions: std::mem::take(&mut current_block_instructions),
+                            terminator,
+                        };
+                        graph.blocks.push(block);
+                        current_block_instructions = Vec::new();
+                    }
+                    current_label = new_label;
+                }
+            }
+        }
+
+        if !current_block_instructions.is_empty() || current_label.is_some() {
+            let block = BasicBlock {
+                label: current_label.take(),
+                instructions: current_block_instructions,
+                terminator: BasicBlockTerminator::Continue,
+            };
+            graph.blocks.push(block);
+        }
+
+        graph
+    }
+
+    pub fn get_first_basic_block(&self) -> Option<BasicBlock> {
+        self.as_control_flow_graph().blocks.into_iter().next()
+    }
+
+    pub fn get_only_basic_block(&self) -> Result<BasicBlock, ProgramContainsMultipleBasicBlocks> {
+        let blocks = self.as_control_flow_graph().blocks;
+        if blocks.len() == 1 {
+            Ok(blocks.into_iter().next().unwrap())
+        } else {
+            Err(ProgramContainsMultipleBasicBlocks)
+        }
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("Program is empty or contains multiple basic blocks")]
+pub struct ProgramContainsMultipleBasicBlocks;
diff --git a/quil-rs/src/program/analysis/mod.rs b/quil-rs/src/program/analysis/mod.rs
new file mode 100644
index 00000000..aeb02704
--- /dev/null
+++ b/quil-rs/src/program/analysis/mod.rs
@@ -0,0 +1,4 @@
+mod control_flow_graph;
+mod stats;
+
+pub use control_flow_graph::{BasicBlock, BasicBlockTerminator, ControlFlowGraph};
diff --git a/quil-rs/src/stats/execution_graph.rs b/quil-rs/src/program/analysis/stats/execution_graph.rs
similarity index 100%
rename from quil-rs/src/stats/execution_graph.rs
rename to quil-rs/src/program/analysis/stats/execution_graph.rs
diff --git a/quil-rs/src/stats/mod.rs b/quil-rs/src/program/analysis/stats/mod.rs
similarity index 88%
rename from quil-rs/src/stats/mod.rs
rename to quil-rs/src/program/analysis/stats/mod.rs
index 8b2395c2..679e4441 100644
--- a/quil-rs/src/stats/mod.rs
+++ b/quil-rs/src/program/analysis/stats/mod.rs
@@ -10,11 +10,13 @@ use std::ops::Neg;
 
 use crate::{
     instruction::{Instruction, Qubit},
-    program::graph::InstructionBlock,
+    program::scheduling::graph::ScheduledBasicBlock,
     Program,
 };
 use execution_graph::{Error as ExecutionGraphError, ExecutionGraph};
 
+use super::control_flow_graph::BasicBlock;
+
 pub trait InstructionsSource {
     type QubitSet;
 
@@ -66,15 +68,41 @@ impl InstructionsSource for Program {
     }
 }
 
+impl<'p> InstructionsSource for BasicBlock<'p> {
+    type QubitSet = HashSet<&'p Qubit>;
+
+    fn is_empty(&self) -> bool {
+        self.instructions().is_empty()
+    }
+
+    fn len(&self) -> usize {
+        self.instructions().len()
+    }
+
+    fn body_instructions(&self) -> impl Iterator<Item = &Instruction> + '_ {
+        self.instructions().iter().copied()
+    }
+
+    fn get_used_qubits(&self) -> &Self::QubitSet {
+        todo!()
+    }
+}
+
 struct InstructionBlockWithQubitSet<'a, 'b> {
-    block: &'a InstructionBlock<'b>,
+    block: &'a BasicBlock<'b>,
     qubits: HashSet<&'a Qubit>,
 }
 
-impl<'a, 'b> From<&'a InstructionBlock<'b>> for InstructionBlockWithQubitSet<'a, 'b> {
-    fn from(block: &'a InstructionBlock<'b>) -> Self {
+impl<'a, 'b> From<&'a ScheduledBasicBlock<'b>> for InstructionBlockWithQubitSet<'a, 'b> {
+    fn from(block: &'a ScheduledBasicBlock<'b>) -> Self {
+        block.basic_block().into()
+    }
+}
+
+impl<'a, 'b> From<&'a BasicBlock<'b>> for InstructionBlockWithQubitSet<'a, 'b> {
+    fn from(block: &'a BasicBlock<'b>) -> Self {
         let qubits = block
-            .instructions
+            .instructions()
             .iter()
             .filter_map(|i| match i {
                 Instruction::Gate(_) | Instruction::Measurement(_) => Some(i.get_qubits()),
@@ -99,7 +127,7 @@ impl<'a> InstructionsSource for InstructionBlockWithQubitSet<'a, '_> {
 
     fn body_instructions(&self) -> impl Iterator<Item = &Instruction> + '_ {
         // 'copied' converts the iterator of `&&Instruction` to an iterator of `&Instruction`
-        self.block.instructions.iter().copied()
+        self.block.instructions().iter().copied()
     }
 
     fn get_used_qubits(&self) -> &Self::QubitSet {
@@ -131,7 +159,19 @@ impl<'a> ProgramStats<&'a Program> {
 }
 
 impl<'a, 'b> ProgramStats<InstructionBlockWithQubitSet<'a, 'b>> {
-    pub fn from_block(block: &'a InstructionBlock<'b>) -> Self {
+    pub fn from_basic_block(block: &'a BasicBlock<'b>) -> Self {
+        let source = block.into();
+        let execution_graph = make_execution_graph(&source);
+
+        Self {
+            source,
+            execution_graph,
+        }
+    }
+}
+
+impl<'a, 'b> ProgramStats<InstructionBlockWithQubitSet<'a, 'b>> {
+    pub fn from_scheduled_basic_block(block: &'a ScheduledBasicBlock<'b>) -> Self {
         let source = block.into();
         let execution_graph = make_execution_graph(&source);
 
@@ -257,8 +297,6 @@ impl<S: InstructionsSource> ProgramStats<S> {
 mod tests {
     use std::f64::consts;
 
-    use crate::instruction::InstructionHandler;
-    use crate::program::graph::ScheduledProgram;
     use crate::Program;
 
     use rstest::rstest;
@@ -275,31 +313,14 @@ mod tests {
     #[case(KITCHEN_SINK_QUIL, &[Qubit::Fixed(0), Qubit::Fixed(1)])]
     fn block_instructions_from_program(#[case] input: &str, #[case] expected: &[Qubit]) {
         let program: Program = input.parse().unwrap();
-        let scheduled =
-            ScheduledProgram::from_program(&program, &mut InstructionHandler::default()).unwrap();
-        let block = scheduled.blocks.first().unwrap().1;
-        let stats = ProgramStats::from_block(block);
+        let block = program.get_first_basic_block().unwrap();
+        let stats = ProgramStats::from_basic_block(&block);
         let qubits = stats.qubits_used();
         let expected = expected.iter().collect::<HashSet<_>>();
 
         assert_eq!(&expected, qubits);
     }
 
-    #[rstest]
-    #[case(QUIL_WITH_JUMP)]
-    #[case(QUIL_WITH_JUMP_WHEN)]
-    #[case(QUIL_WITH_JUMP_UNLESS)]
-    fn program_with_jumps_cannot_be_used_as_block(#[case] input: &str) {
-        let program: Program = input.parse().unwrap();
-        InstructionBlock::build(
-            program.body_instructions().collect(),
-            None,
-            &program,
-            &mut InstructionHandler::default(),
-        )
-        .unwrap_err();
-    }
-
     #[rstest]
     #[case(QUIL_AS_TREE, 3)]
     #[case(QUIL_AS_INVERSE_TREE, 3)]
diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs
index a727dadb..802960dc 100644
--- a/quil-rs/src/program/mod.rs
+++ b/quil-rs/src/program/mod.rs
@@ -33,11 +33,12 @@ pub use self::frame::FrameSet;
 pub use self::frame::MatchedFrames;
 pub use self::memory::{MemoryAccesses, MemoryRegion};
 
+pub mod analysis;
 mod calibration;
 mod error;
 pub(crate) mod frame;
-pub mod graph;
 mod memory;
+pub mod scheduling;
 pub mod type_check;
 
 #[derive(Debug, thiserror::Error, PartialEq)]
@@ -60,9 +61,6 @@ pub enum ProgramError {
 
 type Result<T> = std::result::Result<T, ProgramError>;
 
-#[cfg(feature = "graphviz-dot")]
-pub mod graphviz_dot;
-
 /// A Quil Program instance describes a quantum program with metadata used in execution.
 ///
 /// This contains not only instructions which are executed in turn on the quantum processor, but
diff --git a/quil-rs/src/program/graph.rs b/quil-rs/src/program/scheduling/graph.rs
similarity index 69%
rename from quil-rs/src/program/graph.rs
rename to quil-rs/src/program/scheduling/graph.rs
index 45a2e0c9..24f035b7 100644
--- a/quil-rs/src/program/graph.rs
+++ b/quil-rs/src/program/scheduling/graph.rs
@@ -16,17 +16,14 @@
 
 use std::collections::{HashMap, HashSet};
 
-use indexmap::IndexMap;
 use petgraph::graphmap::GraphMap;
 use petgraph::Directed;
 
-use crate::instruction::{
-    FrameIdentifier, Instruction, InstructionHandler, Jump, JumpUnless, JumpWhen, Label,
-    MemoryReference, Target,
-};
+use crate::instruction::{FrameIdentifier, Instruction, InstructionHandler, Target};
+use crate::program::analysis::{BasicBlock, BasicBlockTerminator};
 use crate::{instruction::InstructionRole, program::Program};
 
-pub use super::memory::MemoryAccessType;
+pub use crate::program::memory::MemoryAccessType;
 
 #[derive(Debug, Clone)]
 pub enum ScheduleErrorVariant {
@@ -191,26 +188,13 @@ macro_rules! add_dependency {
 
 pub type DependencyGraph = GraphMap<ScheduledGraphNode, HashSet<ExecutionDependency>, Directed>;
 
-/// An InstructionBlock of a ScheduledProgram is a group of instructions, identified by a string label,
-/// which include no control flow instructions aside from an (optional) terminating control
-/// flow instruction.
+/// A [`ScheduledBasicBlock`] is a wrapper around a [`BasicBlock`] which includes a graph expressing the vector clock
+/// among the instructions according to the Quil specification.
 #[derive(Clone, Debug)]
-pub struct InstructionBlock<'a> {
-    pub instructions: Vec<&'a Instruction>,
+pub struct ScheduledBasicBlock<'a> {
+    basic_block: BasicBlock<'a>,
     pub(super) graph: DependencyGraph,
-    pub terminator: BlockTerminator<'a>,
 }
-
-impl<'a> Default for InstructionBlock<'a> {
-    fn default() -> Self {
-        Self {
-            instructions: Default::default(),
-            graph: Default::default(),
-            terminator: BlockTerminator::Continue,
-        }
-    }
-}
-
 /// PreviousNodes is a structure which helps maintain ordering among instructions which operate on a given frame.
 /// It works similarly to a multiple-reader-single-writer queue, where an instruction which "uses" a frame is like
 /// a writer and an instruction which blocks that frame is like a reader. Multiple instructions may concurrently
@@ -289,11 +273,9 @@ impl PreviousNodes {
     }
 }
 
-impl<'a> InstructionBlock<'a> {
+impl<'a> ScheduledBasicBlock<'a> {
     pub fn build(
-        // The set of instructions in the block, a subset of the `program`.
-        instructions: Vec<&'a Instruction>,
-        terminator: Option<BlockTerminator<'a>>,
+        basic_block: BasicBlock<'a>,
         program: &'a Program,
         custom_handler: &mut InstructionHandler,
     ) -> ScheduleResult<Self> {
@@ -312,7 +294,7 @@ impl<'a> InstructionBlock<'a> {
         // NOTE: this may be refined to serialize by memory region offset rather than by entire region.
         let mut pending_memory_access: HashMap<String, MemoryAccessQueue> = HashMap::new();
 
-        for (index, &instruction) in instructions.iter().enumerate() {
+        for (index, &instruction) in basic_block.instructions().iter().enumerate() {
             let node = graph.add_node(ScheduledGraphNode::InstructionIndex(index));
 
             match custom_handler.role_for_instruction(instruction) {
@@ -440,107 +422,49 @@ impl<'a> InstructionBlock<'a> {
             add_dependency!(graph, dependency.node_id => ScheduledGraphNode::BlockEnd, execution_dependency);
         }
 
-        Ok(InstructionBlock {
-            graph,
-            instructions,
-            terminator: terminator.unwrap_or(BlockTerminator::Continue),
-        })
+        Ok(ScheduledBasicBlock { graph, basic_block })
     }
 
     pub fn get_dependency_graph(&self) -> &DependencyGraph {
         &self.graph
     }
 
+    pub fn instructions(&'a self) -> &[&'a Instruction] {
+        self.basic_block.instructions()
+    }
+
     /// Return a particular-indexed instruction (if present).
     pub fn get_instruction(&self, node_id: usize) -> Option<&Instruction> {
-        self.instructions.get(node_id).copied()
+        self.instructions().get(node_id).copied()
+    }
+
+    pub fn label(&self) -> Option<&Target> {
+        self.basic_block.label()
     }
 
     /// Return the count of executable instructions in this block.
     pub fn len(&self) -> usize {
-        self.instructions.len()
+        self.instructions().len()
     }
 
     /// Return true if this block contains no executable instructions.
     pub fn is_empty(&self) -> bool {
-        self.instructions.is_empty()
+        self.instructions().is_empty()
     }
 
-    pub fn set_exit_condition(&mut self, terminator: BlockTerminator<'a>) {
-        self.terminator = terminator;
+    pub fn terminator(&self) -> &BasicBlockTerminator {
+        self.basic_block.terminator()
     }
-}
 
-#[derive(Clone, Debug, PartialEq)]
-pub enum BlockTerminator<'a> {
-    Conditional {
-        condition: &'a MemoryReference,
-        target: &'a str,
-        jump_if_condition_true: bool,
-    },
-    Unconditional {
-        target: &'a str,
-    },
-    Continue,
-    Halt,
+    pub fn basic_block(&self) -> &BasicBlock<'a> {
+        &self.basic_block
+    }
 }
 
 #[derive(Clone, Debug)]
 pub struct ScheduledProgram<'a> {
-    /// All blocks within the ScheduledProgram, keyed on string label.
-    pub blocks: IndexMap<String, InstructionBlock<'a>>,
-}
-
-/// Builds an [`InstructionBlock`] from provided instructions, terminator, and program, then tracks
-/// the block and its label, and resets the instruction list and label for a future block to use.
-///
-/// The "working block" is that being traversed within the program, accumulating instructions located
-/// between control instructions (such as `LABEL` and 'JUMP`). When such a control instruction is reached,
-/// this function performs the work to close out and store the instructions in the current "working block"
-/// and then reset the state to prepare for the next block.
-///
-/// # Errors
-///
-/// If an error occurs, `working_instructions` and/or `working_label` may have been emptied and
-/// cannot be relied on to be unchanged.
-fn terminate_working_block<'a>(
-    terminator: Option<BlockTerminator<'a>>,
-    working_instructions: &mut Vec<&'a Instruction>,
-    blocks: &mut IndexMap<String, InstructionBlock<'a>>,
-    working_label: &mut Option<&'a str>,
-    program: &'a Program,
-    instruction_index: Option<usize>,
-    custom_handler: &mut InstructionHandler,
-) -> ScheduleResult<()> {
-    // If this "block" has no instructions and no terminator, it's not worth storing - skip it
-    if working_instructions.is_empty() && terminator.is_none() && working_label.is_none() {
-        return Ok(());
-    }
-
-    // This leaves working_instructions and working_label in their respective "empty" states.
-    let block = InstructionBlock::build(
-        std::mem::take(working_instructions),
-        terminator,
-        program,
-        custom_handler,
-    )?;
-
-    let label = working_label
-        .take()
-        .map(String::from)
-        .unwrap_or_else(|| ScheduledProgram::generate_autoincremented_label(blocks));
-
-    if blocks.insert(label.clone(), block).is_some() {
-        return Err(ScheduleError {
-            instruction_index,
-            instruction: Instruction::Label(Label {
-                target: Target::Fixed(label),
-            }),
-            variant: ScheduleErrorVariant::DuplicateLabel,
-        });
-    }
-
-    Ok(())
+    // todo needs getter
+    basic_blocks: Vec<ScheduledBasicBlock<'a>>,
 }
 
 impl<'a> ScheduledProgram<'a> {
@@ -550,160 +474,18 @@ impl<'a> ScheduledProgram<'a> {
         program: &'a Program,
         custom_handler: &mut InstructionHandler,
     ) -> ScheduleResult<Self> {
-        let mut working_label = None;
-        let mut working_instructions: Vec<&'a Instruction> = vec![];
-        let mut blocks = IndexMap::new();
-
-        for (index, instruction) in program.body_instructions().enumerate() {
-            let instruction_index = Some(index);
-            match instruction {
-                Instruction::Arithmetic(_)
-                | Instruction::Comparison(_)
-                | Instruction::BinaryLogic(_)
-                | Instruction::Convert(_)
-                | Instruction::UnaryLogic(_)
-                | Instruction::Capture(_)
-                | Instruction::Delay(_)
-                | Instruction::Fence(_)
-                | Instruction::Include(_)
-                | Instruction::Move(_)
-                | Instruction::Nop
-                | Instruction::Exchange(_)
-                | Instruction::Load(_)
-                | Instruction::Store(_)
-                | Instruction::Pulse(_)
-                | Instruction::SetFrequency(_)
-                | Instruction::SetPhase(_)
-                | Instruction::SetScale(_)
-                | Instruction::ShiftFrequency(_)
-                | Instruction::ShiftPhase(_)
-                | Instruction::SwapPhases(_)
-                | Instruction::RawCapture(_)
-                | Instruction::Reset(_)
-                | Instruction::Wait => {
-                    working_instructions.push(instruction);
-                }
-                Instruction::CalibrationDefinition(_)
-                | Instruction::CircuitDefinition(_)
-                | Instruction::Declaration(_)
-                | Instruction::GateDefinition(_)
-                | Instruction::FrameDefinition(_)
-                | Instruction::MeasureCalibrationDefinition(_)
-                | Instruction::WaveformDefinition(_) => {}
-                Instruction::Pragma(_) => {
-                    working_instructions.push(instruction);
-                }
-                Instruction::Label(Label {
-                    target: Target::Fixed(value),
-                }) => {
-                    terminate_working_block(
-                        None as Option<BlockTerminator>,
-                        &mut working_instructions,
-                        &mut blocks,
-                        &mut working_label,
-                        program,
-                        instruction_index,
-                        custom_handler,
-                    )?;
-
-                    working_label = Some(value);
-                }
-                Instruction::Jump(Jump {
-                    target: Target::Fixed(target),
-                }) => {
-                    terminate_working_block(
-                        Some(BlockTerminator::Unconditional { target }),
-                        &mut working_instructions,
-                        &mut blocks,
-                        &mut working_label,
-                        program,
-                        instruction_index,
-                        custom_handler,
-                    )?;
-                }
-                Instruction::JumpWhen(JumpWhen {
-                    target: Target::Fixed(target),
-                    condition,
-                }) => {
-                    terminate_working_block(
-                        Some(BlockTerminator::Conditional {
-                            target,
-                            condition,
-                            jump_if_condition_true: true,
-                        }),
-                        &mut working_instructions,
-                        &mut blocks,
-                        &mut working_label,
-                        program,
-                        instruction_index,
-                        custom_handler,
-                    )?;
-                }
-                Instruction::JumpUnless(JumpUnless {
-                    target: Target::Fixed(target),
-                    condition,
-                }) => {
-                    terminate_working_block(
-                        Some(BlockTerminator::Conditional {
-                            target,
-                            condition,
-                            jump_if_condition_true: false,
-                        }),
-                        &mut working_instructions,
-                        &mut blocks,
-                        &mut working_label,
-                        program,
-                        instruction_index,
-                        custom_handler,
-                    )?;
-                }
-                Instruction::Halt => terminate_working_block(
-                    Some(BlockTerminator::Halt),
-                    &mut working_instructions,
-                    &mut blocks,
-                    &mut working_label,
-                    program,
-                    instruction_index,
-                    custom_handler,
-                )?,
-                Instruction::Gate(_)
-                | Instruction::Measurement(_)
-                | Instruction::Label(Label {
-                    target: Target::Placeholder(_),
-                })
-                | Instruction::Jump(_)
-                | Instruction::JumpWhen(_)
-                | Instruction::JumpUnless(_) => {
-                    return Err(ScheduleError {
-                        instruction_index,
-                        instruction: instruction.clone(),
-                        variant: ScheduleErrorVariant::UncalibratedInstruction,
-                    })
-                }
-            };
-        }
-
-        terminate_working_block(
-            None as Option<BlockTerminator>,
-            &mut working_instructions,
-            &mut blocks,
-            &mut working_label,
-            program,
-            None,
-            custom_handler,
-        )?;
-
-        Ok(ScheduledProgram { blocks })
+        let control_flow_graph = program.as_control_flow_graph();
+        Ok(Self {
+            basic_blocks: control_flow_graph
+                .into_blocks()
+                .into_iter()
+                .map(|block| ScheduledBasicBlock::build(block, program, custom_handler))
+                .collect::<ScheduleResult<Vec<_>>>()?,
+        })
     }
 
-    fn generate_autoincremented_label(block_labels: &IndexMap<String, InstructionBlock>) -> String {
-        let mut suffix = 0;
-        let mut label = format!("block_{suffix}");
-        while block_labels.get(&label).is_some() {
-            suffix += 1;
-            label = format!("block_{suffix}");
-        }
-        label
+    pub fn basic_blocks(&self) -> &[ScheduledBasicBlock<'_>] {
+        self.basic_blocks.as_ref()
     }
 }
 
@@ -719,7 +501,7 @@ mod tests {
 
         use crate::instruction::PragmaArgument;
         use crate::program::frame::FrameMatchCondition;
-        use crate::program::graphviz_dot::tests::build_dot_format_snapshot_test_case;
+        use crate::program::scheduling::graphviz_dot::tests::build_dot_format_snapshot_test_case;
 
         /// Generates a custom [`InstructionHandler`] that specially handles two `PRAGMA` instructions:
         ///
diff --git a/quil-rs/src/program/graphviz_dot.rs b/quil-rs/src/program/scheduling/graphviz_dot.rs
similarity index 62%
rename from quil-rs/src/program/graphviz_dot.rs
rename to quil-rs/src/program/scheduling/graphviz_dot.rs
index e3249ec9..0d53e38a 100644
--- a/quil-rs/src/program/graphviz_dot.rs
+++ b/quil-rs/src/program/scheduling/graphviz_dot.rs
@@ -17,14 +17,19 @@
 use dot_writer::{Attributes, DotWriter, Shape, Style};
 
 use crate::{
-    program::graph::{
-        BlockTerminator, ExecutionDependency, InstructionBlock, MemoryAccessType,
-        ScheduledGraphNode, ScheduledProgram,
+    instruction::{Jump, JumpUnless, JumpWhen, Target},
+    program::{
+        analysis::BasicBlockTerminator,
+        scheduling::graph::{
+            ExecutionDependency, MemoryAccessType, ScheduledBasicBlock, ScheduledGraphNode,
+        },
     },
     quil::Quil,
 };
 
-impl<'a> InstructionBlock<'a> {
+use super::graph::ScheduledProgram;
+
+impl<'a> ScheduledBasicBlock<'a> {
     /// Given a [`dot_writer::Scope`] representing a subgraph/cluster, write the timing graph for this block into it.
     /// Uses the `node_prefix` argument for namespacing so that node IDs remain unique within the overall graph.
     fn write_dot_format(&self, cluster: &mut dot_writer::Scope, node_prefix: &str) {
@@ -50,7 +55,7 @@ impl<'a> InstructionBlock<'a> {
                         .set_label(&escape_label(&format!(
                             "[{}] {}",
                             index,
-                            self.instructions.get(*index).unwrap().to_quil_or_debug()
+                            self.instructions().get(*index).unwrap().to_quil_or_debug()
                         )));
                 }
             };
@@ -83,6 +88,41 @@ impl<'a> InstructionBlock<'a> {
     }
 }
 
+#[derive(Debug, PartialEq)]
+enum DotFormatBlockLabel<'p> {
+    Unlabeled { index: usize },
+    Labeled(&'p Target),
+}
+
+impl<'p> DotFormatBlockLabel<'p> {
+    fn from_label_or_index(label: Option<&'p Target>, index: usize) -> Self {
+        label
+            .map(DotFormatBlockLabel::Labeled)
+            .unwrap_or(DotFormatBlockLabel::Unlabeled { index })
+    }
+}
+
+impl<'p> From<&'p Target> for DotFormatBlockLabel<'p> {
+    fn from(target: &'p Target) -> Self {
+        DotFormatBlockLabel::Labeled(target)
+    }
+}
+
+impl<'p> From<usize> for DotFormatBlockLabel<'p> {
+    fn from(index: usize) -> Self {
+        DotFormatBlockLabel::Unlabeled { index }
+    }
+}
+
+impl std::fmt::Display for DotFormatBlockLabel<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            DotFormatBlockLabel::Unlabeled { index } => write!(f, "block_{}", index),
+            DotFormatBlockLabel::Labeled(label) => write!(f, "{}", label.to_quil_or_debug()),
+        }
+    }
+}
+
 impl<'a> ScheduledProgram<'a> {
     /// Return a DOT format string (as bytes) for use with Graphviz.
     ///
@@ -100,91 +140,182 @@ impl<'a> ScheduledProgram<'a> {
             writer.set_pretty_print(true);
             let mut digraph = writer.digraph();
 
-            let mut iter = self.blocks.iter().peekable();
-            if let Some((first_label, _)) = iter.peek() {
+            let blocks = self.basic_blocks();
+            println!("blocks: {:?}", blocks);
+            let mut iter = blocks.iter().enumerate().peekable();
+            if let Some((index, first_block)) = iter.peek() {
+                let block_node_label = first_block
+                    .basic_block()
+                    .label()
+                    .map(DotFormatBlockLabel::from)
+                    .unwrap_or(DotFormatBlockLabel::from(*index));
                 digraph.edge(
                     "entry",
-                    get_node_id(&ScheduledGraphNode::BlockStart, first_label),
+                    get_node_id(
+                        &ScheduledGraphNode::BlockStart,
+                        &block_node_label.to_string(),
+                    ),
                 );
             }
             digraph.node_named("entry").set_label("Entry Point");
 
-            while let Some((label, block)) = iter.next() {
-                let node_prefix = label;
+            while let Some((index, scheduled_basic_block)) = iter.next() {
+                let block_node_label = scheduled_basic_block
+                    .basic_block()
+                    .label()
+                    .map(DotFormatBlockLabel::from)
+                    .unwrap_or(DotFormatBlockLabel::from(index));
+                let string_label = block_node_label.to_string();
                 {
                     let mut cluster = digraph.cluster();
-                    cluster.set_label(label);
+                    cluster.set_label(&string_label);
                     cluster.node_attributes().set_style(Style::Filled);
 
-                    block.write_dot_format(&mut cluster, node_prefix);
+                    scheduled_basic_block.write_dot_format(&mut cluster, &string_label);
                 }
 
-                let next_block_label = iter.peek().map(|(next_label, _)| (*next_label).clone());
+                let next_index_and_block = iter.peek();
                 let terminator_source_label =
-                    get_node_id(&ScheduledGraphNode::BlockEnd, node_prefix);
-                match &block.terminator {
-                    BlockTerminator::Conditional {
-                        condition,
-                        target,
-                        jump_if_condition_true,
-                    } => {
-                        let equality_operators = if *jump_if_condition_true {
-                            ("==", "!=")
-                        } else {
-                            ("!=", "==")
-                        };
+                    get_node_id(&ScheduledGraphNode::BlockEnd, &string_label);
+                match &scheduled_basic_block.terminator() {
+                    BasicBlockTerminator::JumpWhen(JumpWhen { target, condition }) => {
                         digraph
                             .edge(
                                 &terminator_source_label,
-                                get_node_id(&ScheduledGraphNode::BlockStart, target),
+                                get_node_id(
+                                    &ScheduledGraphNode::BlockStart,
+                                    &DotFormatBlockLabel::from(target).to_string(),
+                                ),
                             )
                             .attributes()
                             .set_label(
-                                format!(
-                                    "if {} {} 0",
-                                    condition.to_quil_or_debug(),
-                                    equality_operators.0
+                                format!("if {} == 0", condition.to_quil_or_debug(),).as_str(),
+                            );
+                        if let Some((next_index, next_block)) = next_index_and_block {
+                            digraph
+                                .edge(
+                                    &terminator_source_label,
+                                    get_node_id(
+                                        &ScheduledGraphNode::BlockStart,
+                                        &DotFormatBlockLabel::from_label_or_index(
+                                            next_block.label(),
+                                            *next_index,
+                                        )
+                                        .to_string(),
+                                    ),
                                 )
-                                .as_str(),
+                                .attributes()
+                                .set_label(
+                                    format!("if {} != 0", condition.to_quil_or_debug(),).as_str(),
+                                );
+                        };
+                    }
+                    BasicBlockTerminator::JumpUnless(JumpUnless { target, condition }) => {
+                        digraph
+                            .edge(
+                                &terminator_source_label,
+                                get_node_id(
+                                    &ScheduledGraphNode::BlockStart,
+                                    &DotFormatBlockLabel::from(target).to_string(),
+                                ),
+                            )
+                            .attributes()
+                            .set_label(
+                                format!("if {} != 0", condition.to_quil_or_debug(),).as_str(),
                             );
-                        if let Some(next_label) = next_block_label {
+                        if let Some((next_index, next_block)) = next_index_and_block {
                             digraph
                                 .edge(
                                     &terminator_source_label,
-                                    get_node_id(&ScheduledGraphNode::BlockStart, &next_label),
+                                    get_node_id(
+                                        &ScheduledGraphNode::BlockStart,
+                                        &DotFormatBlockLabel::from_label_or_index(
+                                            next_block.label(),
+                                            *next_index,
+                                        )
+                                        .to_string(),
+                                    ),
                                 )
                                 .attributes()
                                 .set_label(
-                                    format!(
-                                        "if {} {} 0",
-                                        condition.to_quil_or_debug(),
-                                        equality_operators.1
-                                    )
-                                    .as_str(),
+                                    format!("if {} == 0", condition.to_quil_or_debug(),).as_str(),
                                 );
                         };
                     }
-                    BlockTerminator::Unconditional { target } => {
+                    // BasicBlockTerminator::Conditional {
+                    //     condition,
+                    //     target,
+                    //     jump_if_condition_true,
+                    // } => {
+                    //     let equality_operators = if *jump_if_condition_true {
+                    //         ("==", "!=")
+                    //     } else {
+                    //         ("!=", "==")
+                    //     };
+                    // digraph
+                    //     .edge(
+                    //         &terminator_source_label,
+                    //         get_node_id(&ScheduledGraphNode::BlockStart, target),
+                    //     )
+                    //     .attributes()
+                    //     .set_label(
+                    //         format!(
+                    //             "if {} {} 0",
+                    //             condition.to_quil_or_debug(),
+                    //             equality_operators.0
+                    //         )
+                    //         .as_str(),
+                    //     );
+                    // if let Some(next_label) = next_block_label {
+                    //     digraph
+                    //         .edge(
+                    //             &terminator_source_label,
+                    //             get_node_id(&ScheduledGraphNode::BlockStart, &next_label),
+                    //         )
+                    //         .attributes()
+                    //         .set_label(
+                    //             format!(
+                    //                 "if {} {} 0",
+                    //                 condition.to_quil_or_debug(),
+                    //                 equality_operators.1
+                    //             )
+                    //             .as_str(),
+                    //         );
+                    // };
+                    // }
+                    BasicBlockTerminator::Jump(Jump { target }) => {
                         digraph
                             .edge(
                                 &terminator_source_label,
-                                get_node_id(&ScheduledGraphNode::BlockStart, target),
+                                get_node_id(
+                                    &ScheduledGraphNode::BlockStart,
+                                    &DotFormatBlockLabel::from(target).to_string(),
+                                ),
                             )
                             .attributes()
                             .set_label("always");
                     }
-                    BlockTerminator::Continue => {
-                        if let Some(next_label) = next_block_label {
+                    BasicBlockTerminator::Continue => {
+                        if let Some((next_index, next_block)) = next_index_and_block {
+                            let node_label: DotFormatBlockLabel<'_> =
+                                DotFormatBlockLabel::from_label_or_index(
+                                    next_block.label(),
+                                    *next_index,
+                                );
+
                             digraph
                                 .edge(
                                     &terminator_source_label,
-                                    get_node_id(&ScheduledGraphNode::BlockStart, &next_label),
+                                    get_node_id(
+                                        &ScheduledGraphNode::BlockStart,
+                                        &node_label.to_string(),
+                                    ),
                                 )
                                 .attributes()
                                 .set_label("always");
                         };
                     }
-                    BlockTerminator::Halt => {}
+                    BasicBlockTerminator::Halt => {}
                 }
             }
         }
@@ -232,7 +363,7 @@ pub(crate) mod tests {
         /// diffs closely to determine if it's only a matter of reformatting.
         macro_rules! build_dot_format_snapshot_test_case {
             ($name: ident, $input: expr) => {
-                crate::program::graphviz_dot::tests::build_dot_format_snapshot_test_case!(
+                crate::program::scheduling::graphviz_dot::tests::build_dot_format_snapshot_test_case!(
                     $name,
                     $input,
                     &mut InstructionHandler::default(),
@@ -241,7 +372,7 @@ pub(crate) mod tests {
             ($name: ident, $input:expr, $handler: expr $(,)?) => {
                 #[test]
                 fn $name() {
-                    use crate::program::graph::ScheduledProgram;
+                    use crate::program::scheduling::graph::ScheduledProgram;
                     const FRAME_DEFINITIONS: &'static str = "
 DEFFRAME 0 \"rf\":
     INITIAL-FREQUENCY: 1e6
@@ -263,7 +394,7 @@ DEFFRAME 0 1 \"cz\":
                     let scheduled_program =
                         ScheduledProgram::from_program(&program, $handler).unwrap();
 
-                    for block in scheduled_program.blocks.values() {
+                    for block in scheduled_program.basic_blocks() {
                         let graph = block.get_dependency_graph();
                         assert!(
                             !petgraph::algo::is_cyclic_directed(graph),
diff --git a/quil-rs/src/program/scheduling/mod.rs b/quil-rs/src/program/scheduling/mod.rs
new file mode 100644
index 00000000..7ecc21a3
--- /dev/null
+++ b/quil-rs/src/program/scheduling/mod.rs
@@ -0,0 +1,6 @@
+pub(crate) mod graph;
+
+#[cfg(feature = "graphviz-dot")]
+pub(crate) mod graphviz_dot;
+
+pub use graph::{ScheduledBasicBlock, ScheduledProgram};


Test patch:
diff --git a/quil-rs/src/stats/test_programs.rs b/quil-rs/src/program/analysis/stats/test_programs.rs
similarity index 100%
rename from quil-rs/src/stats/test_programs.rs
rename to quil-rs/src/program/analysis/stats/test_programs.rs
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__mixed_pragmas_and_pulses.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__mixed_pragmas_and_pulses.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__mixed_pragmas_and_pulses.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__mixed_pragmas_and_pulses.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__only_pragmas_with_frames.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__only_pragmas_with_frames.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__only_pragmas_with_frames.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__only_pragmas_with_frames.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__only_pragmas_without_frames.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__only_pragmas_without_frames.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__only_pragmas_without_frames.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__custom_handler__only_pragmas_without_frames.snap
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__active_reset_single_frame.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__active_reset_single_frame.snap
new file mode 100644
index 00000000..570efbbd
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__active_reset_single_frame.snap
@@ -0,0 +1,50 @@
+---
+source: quil-rs/src/program/scheduling/graphviz_dot.rs
+expression: dot_format
+---
+digraph {
+  entry -> "@measure_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="@measure";
+    node [style="filled"];
+    "@measure_start" [shape=circle, label="start"];
+    "@measure_start" -> "@measure_0" [label="ordering
+timing"];
+    "@measure_start" -> "@measure_1" [label="ordering
+timing"];
+    "@measure_start" -> "@measure_end" [label="ordering"];
+    "@measure_0" [shape=rectangle, label="[0] NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1000000)"];
+    "@measure_0" -> "@measure_end" [label="ordering
+timing"];
+    "@measure_1" [shape=rectangle, label="[1] NONBLOCKING CAPTURE 0 \"ro_rx\" test(duration: 1000000) ro[0]"];
+    "@measure_1" -> "@measure_end" [label="await capture
+ordering
+timing"];
+    "@measure_end" [shape=circle, label="end"];
+  }
+  "@measure_end" -> "@end_start" [label="if ro[0] == 0"];
+  "@measure_end" -> "@feedback_start" [label="if ro[0] != 0"];
+  subgraph cluster_1 {
+    label="@feedback";
+    node [style="filled"];
+    "@feedback_start" [shape=circle, label="start"];
+    "@feedback_start" -> "@feedback_0" [label="ordering
+timing"];
+    "@feedback_start" -> "@feedback_end" [label="ordering
+timing"];
+    "@feedback_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
+    "@feedback_0" -> "@feedback_end" [label="ordering
+timing"];
+    "@feedback_end" [shape=circle, label="end"];
+  }
+  "@feedback_end" -> "@measure_start" [label="always"];
+  subgraph cluster_2 {
+    label="@end";
+    node [style="filled"];
+    "@end_start" [shape=circle, label="start"];
+    "@end_start" -> "@end_end" [label="ordering"];
+    "@end_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_2q_pulse.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_2q_pulse.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_2q_pulse.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_2q_pulse.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_pulses_after_nonblocking.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_pulses_after_nonblocking.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_pulses_after_nonblocking.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_pulses_after_nonblocking.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_pulses_wrap_nonblocking.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_pulses_wrap_nonblocking.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__blocking_pulses_wrap_nonblocking.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__blocking_pulses_wrap_nonblocking.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__chained_pulses.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__chained_pulses.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__chained_pulses.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__chained_pulses.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__different_frames_blocking.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__different_frames_blocking.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_nonblocking.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__different_frames_nonblocking.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_nonblocking.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__different_frames_nonblocking.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_all.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_all.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_all.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_all.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_all_with_nonblocking_pulses.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_all_with_nonblocking_pulses.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_all_with_nonblocking_pulses.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_all_with_nonblocking_pulses.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_one_wrapper.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_one_wrapper.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_one_wrapper.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_one_wrapper.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_wrapper.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_wrapper.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__fence_wrapper.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__fence_wrapper.snap
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__jump.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__jump.snap
new file mode 100644
index 00000000..4ea0d20e
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__jump.snap
@@ -0,0 +1,51 @@
+---
+source: quil-rs/src/program/scheduling/graphviz_dot.rs
+expression: dot_format
+---
+digraph {
+  entry -> "@first-block_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="@first-block";
+    node [style="filled"];
+    "@first-block_start" [shape=circle, label="start"];
+    "@first-block_start" -> "@first-block_0" [label="ordering
+timing"];
+    "@first-block_start" -> "@first-block_end" [label="ordering
+timing"];
+    "@first-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
+    "@first-block_0" -> "@first-block_end" [label="ordering
+timing"];
+    "@first-block_end" [shape=circle, label="end"];
+  }
+  "@first-block_end" -> "@third-block_start" [label="if ro[0] != 0"];
+  "@first-block_end" -> "@second-block_start" [label="if ro[0] == 0"];
+  subgraph cluster_1 {
+    label="@second-block";
+    node [style="filled"];
+    "@second-block_start" [shape=circle, label="start"];
+    "@second-block_start" -> "@second-block_0" [label="ordering
+timing"];
+    "@second-block_start" -> "@second-block_end" [label="ordering
+timing"];
+    "@second-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
+    "@second-block_0" -> "@second-block_end" [label="ordering
+timing"];
+    "@second-block_end" [shape=circle, label="end"];
+  }
+  "@second-block_end" -> "@third-block_start" [label="always"];
+  subgraph cluster_2 {
+    label="@third-block";
+    node [style="filled"];
+    "@third-block_start" [shape=circle, label="start"];
+    "@third-block_start" -> "@third-block_0" [label="ordering
+timing"];
+    "@third-block_start" -> "@third-block_end" [label="ordering
+timing"];
+    "@third-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
+    "@third-block_0" -> "@third-block_end" [label="ordering
+timing"];
+    "@third-block_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__labels_only.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__labels_only.snap
new file mode 100644
index 00000000..231349d3
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__labels_only.snap
@@ -0,0 +1,16 @@
+---
+source: quil-rs/src/program/scheduling/graphviz_dot.rs
+expression: dot_format
+---
+digraph {
+  entry -> "@c_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="@c";
+    node [style="filled"];
+    "@c_start" [shape=circle, label="start"];
+    "@c_start" -> "@c_end" [label="ordering"];
+    "@c_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__multiple_classical_instructions.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__multiple_classical_instructions.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__multiple_classical_instructions.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__multiple_classical_instructions.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulse.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__parametric_pulse.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulse.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__parametric_pulse.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__parametric_pulses_using_capture_results.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__pulse_after_capture.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__pulse_after_capture.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__pulse_after_capture.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__pulse_after_capture.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__pulse_after_set_frequency.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__pulse_after_set_frequency.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__pulse_after_set_frequency.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__pulse_after_set_frequency.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__simple_capture.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__simple_capture.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__simple_capture.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__simple_capture.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__simple_memory_access.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__simple_memory_access.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__simple_memory_access.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__simple_memory_access.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__single_dependency.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__single_dependency.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__single_dependency.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__single_dependency.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__single_instruction.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__single_instruction.snap
similarity index 100%
rename from quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__single_instruction.snap
rename to quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graphviz_dot__tests__graph__single_instruction.snap
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__active_reset_single_frame.snap b/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__active_reset_single_frame.snap
deleted file mode 100644
index de3b22ed..00000000
--- a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__active_reset_single_frame.snap
+++ /dev/null
@@ -1,50 +0,0 @@
----
-source: src/program/graphviz_dot.rs
-expression: dot_format
----
-digraph {
-  entry -> "measure_start";
-  entry [label="Entry Point"];
-  subgraph cluster_0 {
-    label="measure";
-    node [style="filled"];
-    "measure_start" [shape=circle, label="start"];
-    "measure_start" -> "measure_0" [label="ordering
-timing"];
-    "measure_start" -> "measure_1" [label="ordering
-timing"];
-    "measure_start" -> "measure_end" [label="ordering"];
-    "measure_0" [shape=rectangle, label="[0] NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1000000)"];
-    "measure_0" -> "measure_end" [label="ordering
-timing"];
-    "measure_1" [shape=rectangle, label="[1] NONBLOCKING CAPTURE 0 \"ro_rx\" test(duration: 1000000) ro[0]"];
-    "measure_1" -> "measure_end" [label="await capture
-ordering
-timing"];
-    "measure_end" [shape=circle, label="end"];
-  }
-  "measure_end" -> "end_start" [label="if ro[0] == 0"];
-  "measure_end" -> "feedback_start" [label="if ro[0] != 0"];
-  subgraph cluster_1 {
-    label="feedback";
-    node [style="filled"];
-    "feedback_start" [shape=circle, label="start"];
-    "feedback_start" -> "feedback_0" [label="ordering
-timing"];
-    "feedback_start" -> "feedback_end" [label="ordering
-timing"];
-    "feedback_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
-    "feedback_0" -> "feedback_end" [label="ordering
-timing"];
-    "feedback_end" [shape=circle, label="end"];
-  }
-  "feedback_end" -> "measure_start" [label="always"];
-  subgraph cluster_2 {
-    label="end";
-    node [style="filled"];
-    "end_start" [shape=circle, label="start"];
-    "end_start" -> "end_end" [label="ordering"];
-    "end_end" [shape=circle, label="end"];
-  }
-}
-
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__jump.snap b/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__jump.snap
deleted file mode 100644
index 4de76305..00000000
--- a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__jump.snap
+++ /dev/null
@@ -1,51 +0,0 @@
----
-source: src/program/graphviz_dot.rs
-expression: dot_format
----
-digraph {
-  entry -> "first-block_start";
-  entry [label="Entry Point"];
-  subgraph cluster_0 {
-    label="first-block";
-    node [style="filled"];
-    "first-block_start" [shape=circle, label="start"];
-    "first-block_start" -> "first-block_0" [label="ordering
-timing"];
-    "first-block_start" -> "first-block_end" [label="ordering
-timing"];
-    "first-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
-    "first-block_0" -> "first-block_end" [label="ordering
-timing"];
-    "first-block_end" [shape=circle, label="end"];
-  }
-  "first-block_end" -> "third-block_start" [label="if ro[0] != 0"];
-  "first-block_end" -> "second-block_start" [label="if ro[0] == 0"];
-  subgraph cluster_1 {
-    label="second-block";
-    node [style="filled"];
-    "second-block_start" [shape=circle, label="start"];
-    "second-block_start" -> "second-block_0" [label="ordering
-timing"];
-    "second-block_start" -> "second-block_end" [label="ordering
-timing"];
-    "second-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
-    "second-block_0" -> "second-block_end" [label="ordering
-timing"];
-    "second-block_end" [shape=circle, label="end"];
-  }
-  "second-block_end" -> "third-block_start" [label="always"];
-  subgraph cluster_2 {
-    label="third-block";
-    node [style="filled"];
-    "third-block_start" [shape=circle, label="start"];
-    "third-block_start" -> "third-block_0" [label="ordering
-timing"];
-    "third-block_start" -> "third-block_end" [label="ordering
-timing"];
-    "third-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000)"];
-    "third-block_0" -> "third-block_end" [label="ordering
-timing"];
-    "third-block_end" [shape=circle, label="end"];
-  }
-}
-
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__labels_only.snap b/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__labels_only.snap
deleted file mode 100644
index b610baeb..00000000
--- a/quil-rs/src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__labels_only.snap
+++ /dev/null
@@ -1,32 +0,0 @@
----
-source: src/program/graphviz_dot.rs
-expression: dot_format
----
-digraph {
-  entry -> "a_start";
-  entry [label="Entry Point"];
-  subgraph cluster_0 {
-    label="a";
-    node [style="filled"];
-    "a_start" [shape=circle, label="start"];
-    "a_start" -> "a_end" [label="ordering"];
-    "a_end" [shape=circle, label="end"];
-  }
-  "a_end" -> "b_start" [label="always"];
-  subgraph cluster_1 {
-    label="b";
-    node [style="filled"];
-    "b_start" [shape=circle, label="start"];
-    "b_start" -> "b_end" [label="ordering"];
-    "b_end" [shape=circle, label="end"];
-  }
-  "b_end" -> "c_start" [label="always"];
-  subgraph cluster_2 {
-    label="c";
-    node [style="filled"];
-    "c_start" [shape=circle, label="start"];
-    "c_start" -> "c_end" [label="ordering"];
-    "c_end" [shape=circle, label="end"];
-  }
-}
-