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

Issue:
Reduce number of classical instruction edges in InstructionBlock::gra…
The ultimate goal of this PR is to simplify the `InstructionBlock::graph` and thereby improve the ability of a hardware scheduler to schedule a Quil program by reducing the number of edges between classical instructions and block start/end.

Two types of edges in the `InstructionBlock::graph` are deemed extraneous in this work:

1. An edge from two consecutive classical instructions that read from / write to different memory regions. Rather than insist on classical instruction execute in the particular order they are written in a Quil program, _only_ represent ordering of classical Quil instructions based upon the memory regions they read and write to (this is already done with `MemoryAccessQueue`).
2. Edges from BlockStart, if the node has other incoming edges, or to BlockEnd, if the node has other outgoing edges. _Note that while this could apply equally to `InstructionRole::RfControl`, this change set deliberately only impacts edges to and from classical instructions._

```quil
DECLARE params1 REAL[10]
DECLARE params2 REAL[10]
DECLARE params3 REAL[10]
DECLARE params4 REAL[10]
DECLARE integer1 INTEGER[10]

LOAD params1[0] params2 integers[0] # LOAD_1 in graphs below
SHIFT-PHASE 0 "rf" params1[0]
LOAD params3[0] params4 integers[0] # LOAD_2 in graphs below
```

Old representation of `InstructionBlock::graph`.

```mermaid
flowchart TD
    BlockStart --> LOAD_1
    BlockStart --> SHIFT-PHASE
    LOAD_1 --> SHIFT-PHASE
    LOAD_1 --> LOAD_2
    LOAD_1 --> BlockEnd
    LOAD_2 --> BlockEnd
    SHIFT-PHASE --> BlockEnd
```
_In the original algorithm, there is an edge from LOAD_1 to BlockEnd because there are pending reads on `params1` and `integers`._

New representation of `InstructionBlock::graph`.

```mermaid
flowchart TD
    BlockStart --> LOAD_1
    BlockStart --> LOAD_2
    LOAD_1 --> SHIFT-PHASE
    LOAD_2 --> BlockEnd
    SHIFT-PHASE --> BlockEnd
```

There are three invariants of the `InstructionBlock::graph` that remain unchanged:

1. There is always a path connecting the BlockStart to any classical node in the graph.
1. There is always a path connecting any classical node in the graph to the BlockEnd.
1. These invariants pertain to the BlockStart and BlockEnd as well. In other words, there is always a connecting path from the BlockStart to the BlockEnd regardless as to whether there are any instructions in the block. 



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index deaea770..d91fab29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,5 @@ __pycache__
 # quil-py documentation
 quil-py/build
 
+# unversioned developer notes
+scratch/
diff --git a/Cargo.lock b/Cargo.lock
index 800d9578..9ceff37f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1086,7 +1086,7 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
 
 [[package]]
 name = "quil-cli"
-version = "0.1.1-rc.0"
+version = "0.2.1"
 dependencies = [
  "anyhow",
  "clap",
@@ -1095,7 +1095,7 @@ dependencies = [
 
 [[package]]
 name = "quil-py"
-version = "0.8.1-rc.0"
+version = "0.9.1"
 dependencies = [
  "indexmap",
  "ndarray",
@@ -1109,7 +1109,7 @@ dependencies = [
 
 [[package]]
 name = "quil-rs"
-version = "0.24.1-rc.0"
+version = "0.25.1"
 dependencies = [
  "approx",
  "clap",
diff --git a/deny.toml b/deny.toml
index d3e16e5b..ec107f0b 100644
--- a/deny.toml
+++ b/deny.toml
@@ -24,7 +24,9 @@ yanked = "warn"
 notice = "deny"
 # A list of advisory IDs to ignore. Note that ignored advisories will still
 # output a note when they are encountered.
-ignore = []
+ignore = [
+    "RUSTSEC-2024-0320"  # yaml-rust is unmaintained, dependency of insta
+]
 
 # This section is considered when running `cargo deny check licenses`
 # More documentation for the licenses section can be found here:
diff --git a/quil-rs/Cargo.toml b/quil-rs/Cargo.toml
index e88b7eb1..fa5f5f5f 100644
--- a/quil-rs/Cargo.toml
+++ b/quil-rs/Cargo.toml
@@ -5,7 +5,7 @@ version = "0.25.1"
 edition = "2021"
 rust-version = "1.70"
 license = "Apache-2.0"
-repository = "https://github.com/rigetti/quil-rust"
+repository = "https://github.com/rigetti/quil-rs"
 keywords = ["Quil", "Quantum", "Rigetti"]
 categories = ["parser-implementations", "science", "compilers", "emulators"]
 
diff --git a/quil-rs/src/program/scheduling/graph.rs b/quil-rs/src/program/scheduling/graph.rs
index a67806c6..4d699676 100644
--- a/quil-rs/src/program/scheduling/graph.rs
+++ b/quil-rs/src/program/scheduling/graph.rs
@@ -93,10 +93,6 @@ pub enum ExecutionDependency {
 /// A data structure to be used in the serializing of access to a memory region.
 /// This utility helps guarantee strong consistency in a single-writer, multiple-reader model.
 impl MemoryAccessQueue {
-    pub fn flush(mut self) -> Vec<MemoryAccessDependency> {
-        self.get_blocking_nodes(ScheduledGraphNode::BlockEnd, &MemoryAccessType::Capture)
-    }
-
     /// Register that a node wants access of the given type, while returning which accesses block
     /// the requested access.
     ///
@@ -290,7 +286,10 @@ impl<'a> ScheduledBasicBlock<'a> {
         // Root node
         graph.add_node(ScheduledGraphNode::BlockStart);
 
-        let mut last_classical_instruction = ScheduledGraphNode::BlockStart;
+        // The set of classical instructions that do not have outgoing edges (i.e. there are no
+        // downstream instructions that depend on them). After iterating over all instructions,
+        // the set of trailing classical instructions will need an outgoing edge to the block end.
+        let mut trailing_classical_instructions: HashSet<ScheduledGraphNode> = HashSet::new();
 
         // Store the instruction index of the last instruction to block that frame
         let mut last_instruction_by_frame: HashMap<FrameIdentifier, PreviousNodes> = HashMap::new();
@@ -304,12 +303,52 @@ impl<'a> ScheduledBasicBlock<'a> {
         for (index, &instruction) in basic_block.instructions().iter().enumerate() {
             let node = graph.add_node(ScheduledGraphNode::InstructionIndex(index));
 
+            let accesses = custom_handler.memory_accesses(instruction);
+
+            let memory_dependencies = [
+                (accesses.reads, MemoryAccessType::Read),
+                (accesses.writes, MemoryAccessType::Write),
+                (accesses.captures, MemoryAccessType::Capture),
+            ]
+            .iter()
+            .flat_map(|(regions, access_type)| {
+                regions
+                    .iter()
+                    .flat_map(|region| {
+                        pending_memory_access
+                            .entry(region.clone())
+                            .or_default()
+                            // NOTE: This mutates the underlying `MemoryAccessQueue` by registering
+                            // the instruction node.
+                            .get_blocking_nodes(node, access_type)
+                    })
+                    // Collecting is necessary to avoid "captured variable cannot escape FnMut closure body" errors
+                    .collect::<Vec<_>>()
+            })
+            .collect::<Vec<_>>();
+            let has_memory_dependencies = !memory_dependencies.is_empty();
+            for memory_dependency in memory_dependencies {
+                // Test to make sure that no instructions depend directly on themselves
+                if memory_dependency.node_id != node {
+                    let execution_dependency =
+                        ExecutionDependency::AwaitMemoryAccess(memory_dependency.access_type);
+                    add_dependency!(graph, memory_dependency.node_id => node, execution_dependency);
+                    // This memory dependency now has an outgoing edge, so it is no longer a trailing classical
+                    // instruction. If the memory dependency is not a classical instruction, this
+                    // has no effect.
+                    trailing_classical_instructions.remove(&memory_dependency.node_id);
+                }
+            }
+
             match custom_handler.role_for_instruction(instruction) {
                 // Classical instructions must be ordered by appearance in the program
                 InstructionRole::ClassicalCompute => {
-                    add_dependency!(graph, last_classical_instruction => node, ExecutionDependency::StableOrdering);
-
-                    last_classical_instruction = node;
+                    // If this instruction has no memory dependencies, it is a leading classical
+                    // instruction and needs an incoming edge from the block start.
+                    if !has_memory_dependencies {
+                        add_dependency!(graph, ScheduledGraphNode::BlockStart => node, ExecutionDependency::StableOrdering);
+                    }
+                    trailing_classical_instructions.insert(node);
                     Ok(())
                 }
                 InstructionRole::RFControl => {
@@ -373,34 +412,13 @@ impl<'a> ScheduledBasicBlock<'a> {
                     variant: ScheduleErrorVariant::UnschedulableInstruction,
                 }),
             }?;
-
-            let accesses = custom_handler.memory_accesses(instruction);
-            for (regions, access_type) in [
-                (accesses.reads, MemoryAccessType::Read),
-                (accesses.writes, MemoryAccessType::Write),
-                (accesses.captures, MemoryAccessType::Capture),
-            ] {
-                for region in regions {
-                    let memory_dependencies = pending_memory_access
-                        .entry(region.clone())
-                        .or_default()
-                        .get_blocking_nodes(node, &access_type);
-                    for memory_dependency in memory_dependencies {
-                        // Test to make sure that no instructions depend directly on themselves
-                        if memory_dependency.node_id != node {
-                            let execution_dependency = ExecutionDependency::AwaitMemoryAccess(
-                                memory_dependency.access_type,
-                            );
-                            add_dependency!(graph, memory_dependency.node_id => node, execution_dependency);
-                        }
-                    }
-                }
-            }
         }
 
         // Link all pending dependency nodes to the end of the block, to ensure that the block
         // does not terminate until these are complete
-        add_dependency!(graph, last_classical_instruction => ScheduledGraphNode::BlockEnd, ExecutionDependency::StableOrdering);
+        for trailing_classical_instruction in trailing_classical_instructions {
+            add_dependency!(graph, trailing_classical_instruction => ScheduledGraphNode::BlockEnd, ExecutionDependency::StableOrdering);
+        }
 
         for previous_nodes in last_timed_instruction_by_frame.into_values() {
             for node in previous_nodes.into_hashset() {
@@ -414,19 +432,9 @@ impl<'a> ScheduledBasicBlock<'a> {
             }
         }
 
-        // Examine all "pending" memory operations for all regions
-        let remaining_dependencies = pending_memory_access
-            .into_iter()
-            .flat_map(|(_, queue)| queue.flush())
-            .collect::<Vec<MemoryAccessDependency>>();
-
-        // For each dependency, insert or overwrite an edge in the graph connecting the node pending that
-        // operation to the end of the graph.
-        for dependency in remaining_dependencies {
-            let execution_dependency =
-                ExecutionDependency::AwaitMemoryAccess(dependency.access_type);
-
-            add_dependency!(graph, dependency.node_id => ScheduledGraphNode::BlockEnd, execution_dependency);
+        // Maintain the invariant that the block start node has a connecting path to the block end node.
+        if basic_block.instructions().is_empty() {
+            add_dependency!(graph, ScheduledGraphNode::BlockStart => ScheduledGraphNode::BlockEnd, ExecutionDependency::StableOrdering);
         }
 
         Ok(ScheduledBasicBlock { graph, basic_block })
@@ -525,14 +533,17 @@ impl<'a> From<ScheduledBasicBlock<'a>> for ScheduledBasicBlockOwned {
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+    #[cfg(feature = "graphviz-dot")]
+    use crate::program::scheduling::graphviz_dot::tests::build_dot_format_snapshot_test_case;
+
     #[cfg(feature = "graphviz-dot")]
     mod custom_handler {
-        use super::super::*;
+        use super::*;
 
         use crate::instruction::Pragma;
         use crate::instruction::PragmaArgument;
         use crate::program::frame::FrameMatchCondition;
-        use crate::program::scheduling::graphviz_dot::tests::build_dot_format_snapshot_test_case;
         use crate::program::{MatchedFrames, MemoryAccesses};
 
         /// Generates a custom [`InstructionHandler`] that specially handles two `PRAGMA` instructions:
@@ -670,4 +681,135 @@ PRAGMA RAW-INSTRUCTION foo
             &mut get_custom_handler(),
         }
     }
+
+    // Because any instruction that reads a particular region must be preceded by any earlier instructions that write to/ capture that memory region,
+    // we expect an edge from the first load to the second (0 -> 1).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_write_read_load_load,
+        r#"
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE params3 REAL[1]
+DECLARE integers INTEGER[1]
+LOAD params2[0] params3 integers[0] # writes params2 
+LOAD params1[0] params2 integers[0] # reads params2
+"#
+    }
+
+    // Because any instruction that reads a particular region must be preceded by any earlier instructions that write to/ capture that memory region,
+    // we expect an edge from the mul to the load (0 -> 1).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_write_read_mul_load,
+        r#"
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE integers INTEGER[1]
+
+MUL params2[0] 2 # reads and writes params2
+LOAD params1[0] params2 integers[0] # just reads params2
+"#
+    }
+
+    // Because any instruction that reads a particular region must be preceded by any earlier instructions that write to/ capture that memory region,
+    // we expect an edge from the mul to the add (0 -> 1).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_write_read_add_mul,
+        r#"
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE integers INTEGER[1]
+
+ADD params1[0] 1 # this reads and writes params1
+MUL params1[0] 2 # this reads and writes params1
+"#
+    }
+
+    // Because any instruction that reads a particular region must precede any later instructions that write to/ capture that memory region,
+    // we expect an edge from the first load to the second (0, 1).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_read_write_load_load,
+        r#"
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE integers INTEGER[1]
+
+LOAD params1[0] params2 integers[0] # reads params2
+LOAD params2[0] params3 integers[0] # writes params2
+"#
+    }
+
+    // Because any instruction that reads a particular region must precede any later instructions that write to/ capture that memory region,
+    // we expect an edge from the load to the mul (0, 1).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_read_write_load_mul,
+        r#"
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE integers INTEGER[1]
+
+LOAD params1[0] params2 integers[0] # reads params2
+MUL params2[0] 2                    # reads and writes params2
+"#
+    }
+
+    // Because memory reading and writing dependencies also apply to RfControl instructions, we
+    // expect edges from the first load to the first shift-phase (0 -> 1), the first shift-phase
+    // to the second load (1 -> 2), and the second load to the second shift-phase (2 -> 3).
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        quantum_write_parameterized_operations,
+        r#"
+DEFFRAME 0 "rx":
+    INITIAL-FREQUENCY: 1e8
+DEFFRAME 1 "rx":
+    INITIAL-FREQUENCY: 1e8
+
+DECLARE params1 REAL[1]
+DECLARE params2 REAL[1]
+DECLARE integers INTEGER[1]
+
+LOAD params2[0] params1 integers[0] # writes params2
+SHIFT-PHASE 0 "rf" params2[0]       # reads params2
+LOAD params2[0] params1 integers[1] # writes params2
+SHIFT-PHASE 1 "rf" params2[0]       # reads params2
+"#
+    }
+
+    // Because a pragma by default will have no memory accesses, it should only have edges from the block start and to the block end.
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        classical_no_memory_pragma,
+        r#"PRAGMA example"#
+    }
+
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        write_capture_read,
+        r#"
+DECLARE bits BIT[1]
+DECLARE integers INTEGER[1]
+LOAD bits[0] bits2 integers[0] # write
+NONBLOCKING CAPTURE 0 "Transmon-0_readout_rx" flat(duration: 2.0000000000000003e-06, iq: 1.0, scale: 1.0, phase: 0.8745492960861506, detuning: 0.0) bits[0] # capture
+LOAD bits3[0] bits integers[0] # read
+"#
+    }
+
+    #[cfg(feature = "graphviz-dot")]
+    build_dot_format_snapshot_test_case! {
+        write_write_read,
+        r#"
+DECLARE bits BIT[1]
+DECLARE bits2 BIT[1]
+DECLARE bits3 BIT[1]
+DECLARE integers INTEGER[1]
+LOAD bits[0] bits2 integers[0] # write
+LOAD bits[0] bits3 integers[0] # write
+LOAD bits4[0] bits integers[0] # read
+"#
+    }
 }
diff --git a/scripts/sync_versions.sh b/scripts/sync_versions.sh
index 7632bcb7..49923828 100644
--- a/scripts/sync_versions.sh
+++ b/scripts/sync_versions.sh
@@ -2,6 +2,7 @@
 # This script gets the current version of quil-rs from its manifest, and uses it to update the dependency specification
 # of quil-rs in quil-py's manifset.
 #
+
 # Get current version of quil-rs
 PACKAGE_VERSION=$(cargo metadata --format-version 1 --no-deps | grep -o '"name":"quil-rs","version":"[^"]*' | cut -d '"' -f 8)
 


Test patch:
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_no_memory_pragma.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_no_memory_pragma.snap
new file mode 100644
index 00000000..106272b6
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_no_memory_pragma.snap
@@ -0,0 +1,17 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] PRAGMA example"];
+    "block_0_0" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_load.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_load.snap
new file mode 100644
index 00000000..1dffbf5d
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_load.snap
@@ -0,0 +1,19 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params1[0] params2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await read"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params2[0] params3 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_mul.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_mul.snap
new file mode 100644
index 00000000..ac3d0ae9
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_read_write_load_mul.snap
@@ -0,0 +1,19 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params1[0] params2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await read"];
+    "block_0_1" [shape=rectangle, label="[1] MUL params2[0] 2"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_add_mul.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_add_mul.snap
new file mode 100644
index 00000000..f038dca3
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_add_mul.snap
@@ -0,0 +1,19 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] ADD params1[0] 1"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] MUL params1[0] 2"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_load_load.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_load_load.snap
new file mode 100644
index 00000000..baaff6b7
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_load_load.snap
@@ -0,0 +1,19 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params2[0] params3 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params1[0] params2 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_mul_load.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_mul_load.snap
new file mode 100644
index 00000000..0ec44082
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__classical_write_read_mul_load.snap
@@ -0,0 +1,19 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] MUL params2[0] 2"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params1[0] params2 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index 4a021aaa..a44a4d3b 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -14,20 +14,21 @@ digraph {
 timing"];
     "block_0_start" -> "block_0_2" [label="ordering
 timing"];
+    "block_0_start" -> "block_0_4" [label="ordering"];
     "block_0_start" -> "block_0_5" [label="ordering
 timing"];
     "block_0_start" -> "block_0_7" [label="ordering
 timing"];
     "block_0_start" -> "block_0_8" [label="ordering
 timing"];
+    "block_0_start" -> "block_0_9" [label="ordering"];
     "block_0_start" -> "block_0_end" [label="ordering
 timing"];
     "block_0_0" [shape=rectangle, label="[0] PRAGMA NO-OP"];
-    "block_0_0" -> "block_0_4" [label="ordering"];
+    "block_0_0" -> "block_0_end" [label="ordering"];
     "block_0_1" [shape=rectangle, label="[1] PRAGMA RAW-INSTRUCTION foo"];
     "block_0_1" -> "block_0_3" [label="ordering
 timing"];
-    "block_0_1" -> "block_0_end" [label="await read"];
     "block_0_2" [shape=rectangle, label="[2] PULSE 1 \"bar\" gaussian(duration: 1, fwhm: 2, t0: 3)"];
     "block_0_2" -> "block_0_3" [label="ordering
 timing"];
@@ -38,9 +39,8 @@ timing"];
 timing"];
     "block_0_3" -> "block_0_6" [label="ordering
 timing"];
-    "block_0_3" -> "block_0_end" [label="await read"];
     "block_0_4" [shape=rectangle, label="[4] PRAGMA NO-OP"];
-    "block_0_4" -> "block_0_9" [label="ordering"];
+    "block_0_4" -> "block_0_end" [label="ordering"];
     "block_0_5" [shape=rectangle, label="[5] PULSE 0 \"foo\" gaussian(duration: 1, fwhm: 2, t0: 3)"];
     "block_0_5" -> "block_0_7" [label="ordering
 timing"];
@@ -49,7 +49,6 @@ timing"];
     "block_0_6" [shape=rectangle, label="[6] PRAGMA RAW-INSTRUCTION bar"];
     "block_0_6" -> "block_0_8" [label="ordering
 timing"];
-    "block_0_6" -> "block_0_end" [label="await read"];
     "block_0_7" [shape=rectangle, label="[7] PULSE 0 \"foo\" gaussian(duration: 1, fwhm: 2, t0: 3)"];
     "block_0_7" -> "block_0_10" [label="ordering
 timing"];
@@ -61,8 +60,7 @@ timing"];
     "block_0_9" [shape=rectangle, label="[9] PRAGMA NO-OP"];
     "block_0_9" -> "block_0_end" [label="ordering"];
     "block_0_10" [shape=rectangle, label="[10] PRAGMA RAW-INSTRUCTION foo"];
-    "block_0_10" -> "block_0_end" [label="await read
-ordering
+    "block_0_10" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index 52d468d2..135ea1a2 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -14,29 +14,27 @@ digraph {
 timing"];
     "block_0_start" -> "block_0_2" [label="ordering
 timing"];
+    "block_0_start" -> "block_0_3" [label="ordering"];
+    "block_0_start" -> "block_0_5" [label="ordering"];
     "block_0_0" [shape=rectangle, label="[0] PRAGMA NO-OP"];
-    "block_0_0" -> "block_0_3" [label="ordering"];
+    "block_0_0" -> "block_0_end" [label="ordering"];
     "block_0_1" [shape=rectangle, label="[1] PRAGMA RAW-INSTRUCTION foo"];
     "block_0_1" -> "block_0_4" [label="ordering
 timing"];
-    "block_0_1" -> "block_0_end" [label="await read"];
     "block_0_2" [shape=rectangle, label="[2] PRAGMA RAW-INSTRUCTION bar"];
     "block_0_2" -> "block_0_4" [label="ordering
 timing"];
-    "block_0_2" -> "block_0_end" [label="await read"];
     "block_0_3" [shape=rectangle, label="[3] PRAGMA NO-OP"];
-    "block_0_3" -> "block_0_5" [label="ordering"];
+    "block_0_3" -> "block_0_end" [label="ordering"];
     "block_0_4" [shape=rectangle, label="[4] PRAGMA RAW-INSTRUCTION foo bar"];
     "block_0_4" -> "block_0_6" [label="ordering
 timing"];
-    "block_0_4" -> "block_0_end" [label="await read
-ordering
+    "block_0_4" -> "block_0_end" [label="ordering
 timing"];
     "block_0_5" [shape=rectangle, label="[5] PRAGMA NO-OP"];
     "block_0_5" -> "block_0_end" [label="ordering"];
     "block_0_6" [shape=rectangle, label="[6] PRAGMA RAW-INSTRUCTION foo"];
-    "block_0_6" -> "block_0_end" [label="await read
-ordering
+    "block_0_6" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index c3154246..834b600e 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -12,21 +12,19 @@ digraph {
     "block_0_start" -> "block_0_0" [label="ordering"];
     "block_0_start" -> "block_0_1" [label="ordering
 timing"];
+    "block_0_start" -> "block_0_3" [label="ordering"];
     "block_0_0" [shape=rectangle, label="[0] PRAGMA NO-OP"];
-    "block_0_0" -> "block_0_3" [label="ordering"];
+    "block_0_0" -> "block_0_end" [label="ordering"];
     "block_0_1" [shape=rectangle, label="[1] PRAGMA RAW-INSTRUCTION"];
     "block_0_1" -> "block_0_2" [label="ordering
 timing"];
-    "block_0_1" -> "block_0_end" [label="await read"];
     "block_0_2" [shape=rectangle, label="[2] PRAGMA RAW-INSTRUCTION"];
     "block_0_2" -> "block_0_4" [label="ordering
 timing"];
-    "block_0_2" -> "block_0_end" [label="await read"];
     "block_0_3" [shape=rectangle, label="[3] PRAGMA NO-OP"];
     "block_0_3" -> "block_0_end" [label="ordering"];
     "block_0_4" [shape=rectangle, label="[4] PRAGMA RAW-INSTRUCTION"];
-    "block_0_4" -> "block_0_end" [label="await read
-ordering
+    "block_0_4" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__quantum_write_parameterized_operations.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__quantum_write_parameterized_operations.snap
new file mode 100644
index 00000000..d11ab999
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__quantum_write_parameterized_operations.snap
@@ -0,0 +1,30 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_start" -> "block_0_1" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_3" [label="ordering
+timing"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params2[0] params1 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] SHIFT-PHASE 0 \"rf\" params2[0]"];
+    "block_0_1" -> "block_0_2" [label="await read"];
+    "block_0_1" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD params2[0] params1 integers[1]"];
+    "block_0_2" -> "block_0_3" [label="await write"];
+    "block_0_3" [shape=rectangle, label="[3] SHIFT-PHASE 1 \"rf\" params2[0]"];
+    "block_0_3" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_capture_read.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_capture_read.snap
new file mode 100644
index 00000000..74cf9de9
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_capture_read.snap
@@ -0,0 +1,21 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD bits[0] bits2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] NONBLOCKING CAPTURE 0 \"Transmon-0_readout_rx\" flat(detuning: 0, duration: 2.0000000000000003e-6, iq: 1, phase: 0.8745492960861506, scale: 1) bits[0]"];
+    "block_0_1" -> "block_0_2" [label="await capture"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD bits3[0] bits integers[0]"];
+    "block_0_2" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_write_read.snap b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_write_read.snap
new file mode 100644
index 00000000..879d4291
--- /dev/null
+++ b/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__graph__tests__write_write_read.snap
@@ -0,0 +1,21 @@
+---
+source: quil-rs/src/program/scheduling/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD bits[0] bits2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD bits[0] bits3 integers[0]"];
+    "block_0_1" -> "block_0_2" [label="await write"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD bits4[0] bits integers[0]"];
+    "block_0_2" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
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
index e6307e79..69c2ffe9 100644
--- 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
@@ -13,13 +13,11 @@ digraph {
 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
+    "@measure_1" -> "@measure_end" [label="ordering
 timing"];
     "@measure_end" [shape=circle, label="end"];
   }
@@ -47,4 +45,3 @@ timing"];
     "@end_end" [shape=circle, label="end"];
   }
 }
-
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index b528fb0d..ce5ea1ad 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -15,7 +15,6 @@ timing"];
 timing"];
     "block_0_start" -> "block_0_2" [label="ordering
 timing"];
-    "block_0_start" -> "block_0_end" [label="ordering"];
     "block_0_0" [shape=rectangle, label="[0] NONBLOCKING PULSE 0 \"rf\" test(duration: 1000000)"];
     "block_0_0" -> "block_0_end" [label="ordering
 timing"];
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index d422ef84..47fd881e 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -11,17 +11,13 @@ digraph {
     "block_0_start" [shape=circle, label="start"];
     "block_0_start" -> "block_0_0" [label="ordering"];
     "block_0_0" [shape=rectangle, label="[0] MOVE ro[0] 1"];
-    "block_0_0" -> "block_0_1" [label="await write
-ordering"];
+    "block_0_0" -> "block_0_1" [label="await write"];
     "block_0_1" [shape=rectangle, label="[1] MOVE ro[1] 0"];
-    "block_0_1" -> "block_0_2" [label="await write
-ordering"];
+    "block_0_1" -> "block_0_2" [label="await write"];
     "block_0_2" [shape=rectangle, label="[2] ADD ro[0] 5"];
-    "block_0_2" -> "block_0_3" [label="await write
-ordering"];
+    "block_0_2" -> "block_0_3" [label="await write"];
     "block_0_3" [shape=rectangle, label="[3] SUB ro[1] ro[0]"];
-    "block_0_3" -> "block_0_end" [label="await write
-ordering"];
+    "block_0_3" -> "block_0_end" [label="ordering"];
     "block_0_end" [shape=circle, label="end"];
   }
 }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index ee4eec8b..a85b80bd 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -18,13 +18,10 @@ timing"];
     "block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(a: param[0])"];
     "block_0_0" -> "block_0_1" [label="ordering
 timing"];
-    "block_0_0" -> "block_0_end" [label="await read
-ordering
+    "block_0_0" -> "block_0_end" [label="ordering
 timing"];
     "block_0_1" [shape=rectangle, label="[1] CAPTURE 0 \"ro_rx\" test(a: param[0]) ro[0]"];
-    "block_0_1" -> "block_0_end" [label="await capture
-await read
-ordering
+    "block_0_1" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index 28ea1adb..5dd9c6c8 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -25,8 +25,7 @@ ordering
 timing"];
     "block_0_0" -> "block_0_3" [label="ordering
 timing"];
-    "block_0_0" -> "block_0_end" [label="await read
-ordering
+    "block_0_0" -> "block_0_end" [label="ordering
 timing"];
     "block_0_1" [shape=rectangle, label="[1] NONBLOCKING PULSE 0 \"rf\" test(a: ro[0])"];
     "block_0_1" -> "block_0_3" [label="await read
@@ -42,16 +41,13 @@ timing"];
     "block_0_3" -> "block_0_4" [label="await capture
 ordering
 timing"];
-    "block_0_3" -> "block_0_end" [label="await read
-ordering
+    "block_0_3" -> "block_0_end" [label="ordering
 timing"];
     "block_0_4" [shape=rectangle, label="[4] NONBLOCKING PULSE 0 \"rf\" test(a: ro[0])"];
-    "block_0_4" -> "block_0_end" [label="await read
-ordering
+    "block_0_4" -> "block_0_end" [label="ordering
 timing"];
     "block_0_5" [shape=rectangle, label="[5] NONBLOCKING PULSE 1 \"rf\" test(a: ro[0])"];
-    "block_0_5" -> "block_0_end" [label="await read
-ordering
+    "block_0_5" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index 8e852ca4..f16b1f1c 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -18,8 +18,7 @@ timing"];
     "block_0_0" [shape=rectangle, label="[0] CAPTURE 0 \"ro_rx\" test ro[0]"];
     "block_0_0" -> "block_0_1" [label="ordering
 timing"];
-    "block_0_0" -> "block_0_end" [label="await capture
-ordering
+    "block_0_0" -> "block_0_end" [label="ordering
 timing"];
     "block_0_1" [shape=rectangle, label="[1] PULSE 0 \"rf\" test"];
     "block_0_1" -> "block_0_end" [label="ordering
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index b0158588..2fda2734 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -14,8 +14,7 @@ timing"];
     "block_0_start" -> "block_0_end" [label="ordering
 timing"];
     "block_0_0" [shape=rectangle, label="[0] CAPTURE 0 \"ro_rx\" test ro[0]"];
-    "block_0_0" -> "block_0_end" [label="await capture
-ordering
+    "block_0_0" -> "block_0_end" [label="ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];
   }
diff --git a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
index 891fbd04..9cf875bf 100644
--- a/quil-rs/src/program/scheduling/snapshots/quil_rs__program__scheduling__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
@@ -1,5 +1,5 @@
 ---
-source: src/program/graphviz_dot.rs
+source: quil-rs/src/program/graphviz_dot.rs
 expression: dot_format
 ---
 digraph {
@@ -10,16 +10,13 @@ digraph {
     node [style="filled"];
     "block_0_start" [shape=circle, label="start"];
     "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_start" -> "block_0_1" [label="ordering"];
     "block_0_0" [shape=rectangle, label="[0] MOVE a[0] 1"];
-    "block_0_0" -> "block_0_1" [label="ordering"];
     "block_0_0" -> "block_0_2" [label="await write"];
     "block_0_1" [shape=rectangle, label="[1] MOVE b[0] 2"];
-    "block_0_1" -> "block_0_2" [label="await write
-ordering"];
+    "block_0_1" -> "block_0_2" [label="await write"];
     "block_0_2" [shape=rectangle, label="[2] ADD a[0] b[0]"];
-    "block_0_2" -> "block_0_end" [label="await read
-await write
-ordering"];
+    "block_0_2" -> "block_0_end" [label="ordering"];
     "block_0_end" [shape=circle, label="end"];
   }
 }
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_no_memory_pragma.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_no_memory_pragma.snap
new file mode 100644
index 00000000..4e5e3551
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_no_memory_pragma.snap
@@ -0,0 +1,18 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] PRAGMA example"];
+    "block_0_0" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_load.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_load.snap
new file mode 100644
index 00000000..9910d725
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_load.snap
@@ -0,0 +1,20 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params1[0] params2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await read"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params2[0] params3 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_mul.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_mul.snap
new file mode 100644
index 00000000..063b204b
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_read_write_load_mul.snap
@@ -0,0 +1,20 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params1[0] params2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await read"];
+    "block_0_1" [shape=rectangle, label="[1] MUL params2[0] 2"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_add_mul.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_add_mul.snap
new file mode 100644
index 00000000..8fa23486
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_add_mul.snap
@@ -0,0 +1,20 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] ADD params1[0] 1"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] MUL params1[0] 2"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_load_load.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_load_load.snap
new file mode 100644
index 00000000..e44abe25
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_load_load.snap
@@ -0,0 +1,20 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params2[0] params3 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params1[0] params2 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_mul_load.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_mul_load.snap
new file mode 100644
index 00000000..b8e7545c
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_read_mul_load.snap
@@ -0,0 +1,20 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] MUL params2[0] 2"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD params1[0] params2 integers[0]"];
+    "block_0_1" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_write_load_mul.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_write_load_mul.snap
new file mode 100644
index 00000000..f6f1ad22
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__classical_write_write_load_mul.snap
@@ -0,0 +1,31 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_start" -> "block_0_1" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_3" [label="ordering
+timing"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params2[0] params1 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] SHIFT-PHASE 0 \"rf\" params2[0]"];
+    "block_0_1" -> "block_0_2" [label="await read"];
+    "block_0_1" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD params2[0] params1 integers[1]"];
+    "block_0_2" -> "block_0_3" [label="await write"];
+    "block_0_3" [shape=rectangle, label="[3] SHIFT-PHASE 1 \"rf\" params2[0]"];
+    "block_0_3" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__load_dependencies.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__load_dependencies.snap
new file mode 100644
index 00000000..bbdb85b6
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__custom_handler__load_dependencies.snap
@@ -0,0 +1,126 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_2" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_3" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_9" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_10" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_end" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD param01[0] params01 shot_index0[0]"];
+    "block_0_0" -> "block_0_5" [label="await write"];
+    "block_0_0" -> "block_0_end" [label="await read"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD param02[0] params02 shot_index1[0]"];
+    "block_0_1" -> "block_0_10" [label="await write"];
+    "block_0_1" -> "block_0_end" [label="await read"];
+    "block_0_2" [shape=rectangle, label="[2] FENCE 0"];
+    "block_0_2" -> "block_0_3" [label="ordering
+timing"];
+    "block_0_3" [shape=rectangle, label="[3] SHIFT-PHASE 0 \"rf\" -0.14945171144281807"];
+    "block_0_3" -> "block_0_4" [label="ordering
+timing"];
+    "block_0_4" [shape=rectangle, label="[4] SHIFT-PHASE 0 \"rf\" -0.17773989418736816"];
+    "block_0_4" -> "block_0_5" [label="ordering
+timing"];
+    "block_0_5" [shape=rectangle, label="[5] SHIFT-PHASE 0 \"rf\" -1*param01[0]"];
+    "block_0_5" -> "block_0_8" [label="ordering
+timing"];
+    "block_0_5" -> "block_0_15" [label="ordering
+timing"];
+    "block_0_5" -> "block_0_16" [label="ordering
+timing"];
+    "block_0_5" -> "block_0_24" [label="ordering
+timing"];
+    "block_0_5" -> "block_0_25" [label="ordering
+timing"];
+    "block_0_5" -> "block_0_end" [label="await read
+ordering
+timing"];
+    "block_0_6" [shape=rectangle, label="[6] SHIFT-PHASE 0 1 \"xy\" -0.5*param01[0]"];
+    "block_0_6" -> "block_0_end" [label="await read"];
+    "block_0_7" [shape=rectangle, label="[7] SHIFT-PHASE 0 7 \"xy\" -0.5*param01[0]"];
+    "block_0_7" -> "block_0_end" [label="await read"];
+    "block_0_8" [shape=rectangle, label="[8] FENCE 0"];
+    "block_0_8" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_9" [shape=rectangle, label="[9] FENCE 1"];
+    "block_0_9" -> "block_0_10" [label="ordering
+timing"];
+    "block_0_10" [shape=rectangle, label="[10] SHIFT-PHASE 1 \"rf\" -1*param02[0]"];
+    "block_0_10" -> "block_0_14" [label="ordering
+timing"];
+    "block_0_10" -> "block_0_26" [label="ordering
+timing"];
+    "block_0_10" -> "block_0_27" [label="ordering
+timing"];
+    "block_0_10" -> "block_0_35" [label="ordering
+timing"];
+    "block_0_10" -> "block_0_36" [label="ordering
+timing"];
+    "block_0_10" -> "block_0_end" [label="await read
+ordering
+timing"];
+    "block_0_11" [shape=rectangle, label="[11] SHIFT-PHASE 0 1 \"xy\" 0.5*param02[0]"];
+    "block_0_11" -> "block_0_end" [label="await read"];
+    "block_0_12" [shape=rectangle, label="[12] SHIFT-PHASE 1 16 \"xy\" 0.5*param02[0]"];
+    "block_0_12" -> "block_0_end" [label="await read"];
+    "block_0_13" [shape=rectangle, label="[13] SHIFT-PHASE 1 2 \"xy\" 0.5*param02[0]"];
+    "block_0_13" -> "block_0_end" [label="await read"];
+    "block_0_14" [shape=rectangle, label="[14] FENCE 1"];
+    "block_0_14" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_15" [shape=rectangle, label="[15] FENCE 0"];
+    "block_0_15" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_16" [shape=rectangle, label="[16] PULSE 0 \"rf_f12\" gaussian(detuning: 0, duration: 6.000000000000001e-8, fwhm: 1.5000000000000002e-8, phase: 0, scale: 0.16297407445283926, t0: 3.0000000000000004e-8)"];
+    "block_0_16" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_17" [shape=rectangle, label="[17] NONBLOCKING PULSE 0 \"ro_tx\" flat(detuning: 0, duration: 1.48e-6, iq: 1, phase: 0, scale: 0.070794578438414)"];
+    "block_0_18" [shape=rectangle, label="[18] NONBLOCKING CAPTURE 0 \"ro_rx\" boxcar_kernel(detuning: 0, duration: 1.48e-6, phase: -2.399526580341531, scale: 1) ro[0]"];
+    "block_0_18" -> "block_0_29" [label="await capture"];
+    "block_0_19" [shape=rectangle, label="[19] PRAGMA FILTER-NODE q0_unclassified \"{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q0_ro_rx/filter','publish':true,'params':{},'_type':'FilterNode'}\""];
+    "block_0_20" [shape=rectangle, label="[20] PRAGMA LOAD-MEMORY q0_unclassified \"q0_unclassified[0]\""];
+    "block_0_21" [shape=rectangle, label="[21] PRAGMA FILTER-NODE q0_classified \"{'module':'lodgepole.filters.classifiers','filter_type':'SingleQLinear','source':'q0_ro_rx/filter','publish':false,'params':{'a':[1.0,0.0],'threshold':0.0004483948973233858},'_type':'FilterNode'}\""];
+    "block_0_22" [shape=rectangle, label="[22] PRAGMA FILTER-NODE q0 \"{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q0_classified','publish':true,'params':{},'_type':'FilterNode'}\""];
+    "block_0_23" [shape=rectangle, label="[23] PRAGMA LOAD-MEMORY q0 \"ro[0]\""];
+    "block_0_24" [shape=rectangle, label="[24] PULSE 0 \"rf_f12\" gaussian(detuning: 0, duration: 6.000000000000001e-8, fwhm: 1.5000000000000002e-8, phase: 0, scale: 0.16297407445283926, t0: 3.0000000000000004e-8)"];
+    "block_0_24" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_25" [shape=rectangle, label="[25] FENCE 0"];
+    "block_0_25" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_26" [shape=rectangle, label="[26] FENCE 1"];
+    "block_0_26" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_27" [shape=rectangle, label="[27] PULSE 1 \"rf_f12\" gaussian(detuning: 0, duration: 6.000000000000001e-8, fwhm: 1.5000000000000002e-8, phase: 0, scale: 0.14420836465022144, t0: 3.0000000000000004e-8)"];
+    "block_0_27" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_28" [shape=rectangle, label="[28] NONBLOCKING PULSE 1 \"ro_tx\" flat(detuning: 0, duration: 2e-6, iq: 1, phase: 0, scale: 0.0891250938133745)"];
+    "block_0_29" [shape=rectangle, label="[29] NONBLOCKING CAPTURE 1 \"ro_rx\" boxcar_kernel(detuning: 0, duration: 2e-6, phase: 0.8107443195373324, scale: 1) ro[1]"];
+    "block_0_29" -> "block_0_end" [label="await capture"];
+    "block_0_30" [shape=rectangle, label="[30] PRAGMA FILTER-NODE q1_unclassified \"{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q1_ro_rx/filter','publish':true,'params':{},'_type':'FilterNode'}\""];
+    "block_0_31" [shape=rectangle, label="[31] PRAGMA LOAD-MEMORY q1_unclassified \"q1_unclassified[0]\""];
+    "block_0_32" [shape=rectangle, label="[32] PRAGMA FILTER-NODE q1_classified \"{'module':'lodgepole.filters.classifiers','filter_type':'SingleQLinear','source':'q1_ro_rx/filter','publish':false,'params':{'a':[1.0,0.0],'threshold':5.6536894061896907e-05},'_type':'FilterNode'}\""];
+    "block_0_33" [shape=rectangle, label="[33] PRAGMA FILTER-NODE q1 \"{'module':'lodgepole.filters.io','filter_type':'DataBuffer','source':'q1_classified','publish':true,'params':{},'_type':'FilterNode'}\""];
+    "block_0_34" [shape=rectangle, label="[34] PRAGMA LOAD-MEMORY q1 \"ro[1]\""];
+    "block_0_35" [shape=rectangle, label="[35] PULSE 1 \"rf_f12\" gaussian(detuning: 0, duration: 6.000000000000001e-8, fwhm: 1.5000000000000002e-8, phase: 0, scale: 0.14420836465022144, t0: 3.0000000000000004e-8)"];
+    "block_0_35" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_36" [shape=rectangle, label="[36] FENCE 1"];
+    "block_0_36" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__quantum_write_parameterized_operations.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__quantum_write_parameterized_operations.snap
new file mode 100644
index 00000000..f6f1ad22
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__quantum_write_parameterized_operations.snap
@@ -0,0 +1,31 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_start" -> "block_0_1" [label="ordering
+timing"];
+    "block_0_start" -> "block_0_3" [label="ordering
+timing"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD params2[0] params1 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] SHIFT-PHASE 0 \"rf\" params2[0]"];
+    "block_0_1" -> "block_0_2" [label="await read"];
+    "block_0_1" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD params2[0] params1 integers[1]"];
+    "block_0_2" -> "block_0_3" [label="await write"];
+    "block_0_3" [shape=rectangle, label="[3] SHIFT-PHASE 1 \"rf\" params2[0]"];
+    "block_0_3" -> "block_0_end" [label="ordering
+timing"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_capture_read.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_capture_read.snap
new file mode 100644
index 00000000..2bc54007
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_capture_read.snap
@@ -0,0 +1,22 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD bits[0] bits2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] NONBLOCKING CAPTURE 0 \"Transmon-0_readout_rx\" flat(detuning: 0, duration: 2.0000000000000003e-6, iq: 1, phase: 0.8745492960861506, scale: 1) bits[0]"];
+    "block_0_1" -> "block_0_2" [label="await capture"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD bits3[0] bits integers[0]"];
+    "block_0_2" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+
diff --git a/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_write_read.snap b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_write_read.snap
new file mode 100644
index 00000000..059bf100
--- /dev/null
+++ b/quil-rs/src/program/snapshots/quil_rs__program__graph__tests__write_write_read.snap
@@ -0,0 +1,22 @@
+---
+source: quil-rs/src/program/graph.rs
+expression: dot_format
+---
+digraph {
+  entry -> "block_0_start";
+  entry [label="Entry Point"];
+  subgraph cluster_0 {
+    label="block_0";
+    node [style="filled"];
+    "block_0_start" [shape=circle, label="start"];
+    "block_0_start" -> "block_0_0" [label="ordering"];
+    "block_0_0" [shape=rectangle, label="[0] LOAD bits[0] bits2 integers[0]"];
+    "block_0_0" -> "block_0_1" [label="await write"];
+    "block_0_1" [shape=rectangle, label="[1] LOAD bits[0] bits3 integers[0]"];
+    "block_0_1" -> "block_0_2" [label="await write"];
+    "block_0_2" [shape=rectangle, label="[2] LOAD bits4[0] bits integers[0]"];
+    "block_0_2" -> "block_0_end" [label="ordering"];
+    "block_0_end" [shape=circle, label="end"];
+  }
+}
+