Skip to content

Commit ee0e406

Browse files
authored
Breaking: fix frame dependency calculation (#76)
* Fix: frame dependency calculation * Breaking: Program.get_frames_for_instruction returns HashSet
1 parent 0325730 commit ee0e406

17 files changed

+262
-37
lines changed

src/program/frame.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub(crate) enum FrameMatchCondition<'a> {
129129
/// Match all frames which contain exactly these qubits
130130
ExactQubits(&'a [Qubit]),
131131

132-
/// Return these specific frames, if present in the set
132+
/// Return this specific frame, if present in the set
133133
Specific(&'a FrameIdentifier),
134134

135135
/// Return all frames which match all of these conditions

src/program/graph.rs

+100-13
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,81 @@ pub struct InstructionBlock {
200200
pub terminator: BlockTerminator,
201201
}
202202

203+
/// PreviousNodes is a structure which helps maintain ordering among instructions which operate on a given frame.
204+
/// It works similarly to a multiple-reader-single-writer queue, where an instruction which "uses" a frame is like
205+
/// a writer and an instruction which blocks that frame is like a reader. Multiple instructions may concurrently
206+
/// block a frame, but an instruction may not use a frame while it is concurrently used or blocked.
207+
///
208+
/// ## Examples
209+
///
210+
/// Note that "depends on" is equivalent to "must execute after completion of".
211+
///
212+
/// ```text
213+
/// user --> user # a second user takes a dependency on the first
214+
///
215+
/// user --> blocker # multiple blockers take a dependency on the most recent user
216+
/// \-> blocker
217+
/// \-> blocker
218+
///
219+
/// blocker --> user --> blocker # users and blockers take dependencies on one another,
220+
/// # but blockers do not depend on other blocking instructions
221+
/// ```
222+
struct PreviousNodes {
223+
using: Option<ScheduledGraphNode>,
224+
blocking: HashSet<ScheduledGraphNode>,
225+
}
226+
227+
impl Default for PreviousNodes {
228+
/// The default value for [PreviousNodes] is useful in that, if no previous nodes have been recorded
229+
/// as using a frame, we should consider that the start of the instruction block "blocks" use of that frame
230+
/// (in other words, this instruction cannot be scheduled prior to the start of the instruction block).
231+
fn default() -> Self {
232+
Self {
233+
using: None,
234+
blocking: vec![ScheduledGraphNode::BlockStart].into_iter().collect(),
235+
}
236+
}
237+
}
238+
239+
impl PreviousNodes {
240+
/// Register a node as using a frame, and return the instructions on which it should depend/wait for scheduling (if any).
241+
///
242+
/// A node which uses a frame will block on any previous user or blocker of the frame, much like a writer in a read-write lock.
243+
fn get_dependencies_for_next_user(
244+
&mut self,
245+
node: ScheduledGraphNode,
246+
) -> HashSet<ScheduledGraphNode> {
247+
let mut result = std::mem::take(&mut self.blocking);
248+
if let Some(previous_user) = self.using.replace(node) {
249+
result.insert(previous_user);
250+
}
251+
252+
result
253+
}
254+
255+
/// Register a node as blocking a frame, and return the instructions on which it should depend/wait for scheduling (if any).
256+
///
257+
/// A node which blocks a frame will block on any previous user of the frame, but not concurrent blockers.
258+
///
259+
/// If the frame is currently blocked by other nodes, it will add itself to the list of blockers,
260+
/// much like a reader in a read-write lock.
261+
fn get_dependency_for_next_blocker(
262+
&mut self,
263+
node: ScheduledGraphNode,
264+
) -> Option<ScheduledGraphNode> {
265+
self.blocking.insert(node);
266+
self.using
267+
}
268+
269+
/// Consume the [PreviousNodes] and return all nodes within.
270+
pub fn drain(mut self) -> HashSet<ScheduledGraphNode> {
271+
if let Some(using) = self.using {
272+
self.blocking.insert(using);
273+
}
274+
self.blocking
275+
}
276+
}
277+
203278
impl InstructionBlock {
204279
pub fn build(
205280
instructions: Vec<Instruction>,
@@ -213,8 +288,7 @@ impl InstructionBlock {
213288
let mut last_classical_instruction = ScheduledGraphNode::BlockStart;
214289

215290
// Store the instruction index of the last instruction to block that frame
216-
let mut last_instruction_by_frame: HashMap<FrameIdentifier, ScheduledGraphNode> =
217-
HashMap::new();
291+
let mut last_instruction_by_frame: HashMap<FrameIdentifier, PreviousNodes> = HashMap::new();
218292

219293
// Store memory access reads and writes. Key is memory region name.
220294
// NOTE: this may be refined to serialize by memory region offset rather than by entire region.
@@ -236,21 +310,32 @@ impl InstructionBlock {
236310
let used_frames = program
237311
.get_frames_for_instruction(instruction, false)
238312
.unwrap_or_default();
313+
239314
let blocked_frames = program
240315
.get_frames_for_instruction(instruction, true)
241316
.unwrap_or_default();
242317

243-
// Take a dependency on any previous instructions to _block_ a frame which this instruction _uses_.
244-
for frame in used_frames {
245-
let previous_node_id = last_instruction_by_frame
246-
.get(frame)
247-
.unwrap_or(&ScheduledGraphNode::BlockStart);
248-
add_dependency!(graph, *previous_node_id => node, ExecutionDependency::ReferenceFrame);
318+
let blocked_but_not_used_frames = blocked_frames.difference(&used_frames);
319+
320+
for frame in &used_frames {
321+
let previous_node_ids = last_instruction_by_frame
322+
.entry((*frame).clone())
323+
.or_default()
324+
.get_dependencies_for_next_user(node);
325+
326+
for previous_node_id in previous_node_ids {
327+
add_dependency!(graph, previous_node_id => node, ExecutionDependency::ReferenceFrame);
328+
}
249329
}
250330

251-
// We mark all "blocked" frames as such for later instructions to take a dependency on
252-
for frame in blocked_frames {
253-
last_instruction_by_frame.insert(frame.clone(), node);
331+
for frame in blocked_but_not_used_frames {
332+
if let Some(previous_node_id) = last_instruction_by_frame
333+
.entry((*frame).clone())
334+
.or_default()
335+
.get_dependency_for_next_blocker(node)
336+
{
337+
add_dependency!(graph, previous_node_id => node, ExecutionDependency::ReferenceFrame);
338+
}
254339
}
255340

256341
Ok(())
@@ -295,8 +380,10 @@ impl InstructionBlock {
295380
// does not terminate until these are complete
296381
add_dependency!(graph, last_classical_instruction => ScheduledGraphNode::BlockEnd, ExecutionDependency::StableOrdering);
297382

298-
for (_, last_instruction) in last_instruction_by_frame {
299-
add_dependency!(graph, last_instruction => ScheduledGraphNode::BlockEnd, ExecutionDependency::ReferenceFrame);
383+
for previous_nodes in last_instruction_by_frame.into_values() {
384+
for node in previous_nodes.drain() {
385+
add_dependency!(graph, node => ScheduledGraphNode::BlockEnd, ExecutionDependency::ReferenceFrame);
386+
}
300387
}
301388

302389
// Examine all "pending" memory operations for all regions

src/program/graphviz_dot.rs

+29
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,35 @@ NONBLOCKING PULSE 2 \"rf\" test(duration: 1e6)
300300
"
301301
);
302302

303+
build_dot_format_snapshot_test_case!(
304+
blocking_pulses_wrap_nonblocking,
305+
"
306+
PULSE 0 \"rf\" test(duration: 1e6)
307+
NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1e6)
308+
PULSE 0 \"rf\" test(duration: 1e6)
309+
FENCE 0
310+
FENCE 0
311+
"
312+
);
313+
314+
build_dot_format_snapshot_test_case!(
315+
blocking_pulses_after_nonblocking,
316+
"
317+
NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1e6)
318+
PULSE 0 \"rf\" test(duration: 1e6)
319+
PULSE 0 \"ro_rx\" test(duration: 1e6)
320+
"
321+
);
322+
323+
build_dot_format_snapshot_test_case!(
324+
blocking_2q_pulse,
325+
"
326+
PULSE 0 \"rf\" test(duration: 1e-6)
327+
PULSE 1 \"rf\" test(duration: 1e-6)
328+
PULSE 0 1 \"cz\" test(duration: 1e-6)
329+
"
330+
);
331+
303332
build_dot_format_snapshot_test_case!(
304333
fence_all_with_nonblocking_pulses,
305334
"

src/program/mod.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,10 @@ impl Program {
130130
&'a self,
131131
instruction: &'a Instruction,
132132
include_blocked: bool,
133-
) -> Option<Vec<&'a FrameIdentifier>> {
133+
) -> Option<HashSet<&'a FrameIdentifier>> {
134134
instruction
135135
.get_frame_match_condition(include_blocked)
136-
.map(|condition| {
137-
self.frames
138-
.get_matching_keys(condition)
139-
.into_iter()
140-
.collect()
141-
})
136+
.map(|condition| self.frames.get_matching_keys(condition))
142137
}
143138

144139
/// Returns a HashSet consisting of every Qubit that is used in the program.

src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__active_reset_single_frame.snap

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ frame"];
2626
node [style="filled"];
2727
"feedback_start" [shape=circle, label="start"];
2828
"feedback_start" -> "feedback_0" [label="frame"];
29-
"feedback_start" -> "feedback_end" [label="ordering"];
29+
"feedback_start" -> "feedback_end" [label="frame
30+
ordering"];
3031
"feedback_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
3132
"feedback_0" -> "feedback_end" [label="frame"];
3233
"feedback_end" [shape=circle, label="end"];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: src/program/graphviz_dot.rs
3+
expression: dot_format
4+
---
5+
digraph {
6+
entry -> "block_0_start";
7+
entry [label="Entry Point"];
8+
subgraph cluster_0 {
9+
label="block_0";
10+
node [style="filled"];
11+
"block_0_start" [shape=circle, label="start"];
12+
"block_0_start" -> "block_0_0" [label="frame"];
13+
"block_0_start" -> "block_0_1" [label="frame"];
14+
"block_0_start" -> "block_0_2" [label="frame"];
15+
"block_0_start" -> "block_0_end" [label="frame
16+
ordering"];
17+
"block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1e-6)"];
18+
"block_0_0" -> "block_0_2" [label="frame"];
19+
"block_0_0" -> "block_0_end" [label="frame"];
20+
"block_0_1" [shape=rectangle, label="[1] PULSE 1 \"rf\" test(duration: 1e-6)"];
21+
"block_0_1" -> "block_0_2" [label="frame"];
22+
"block_0_1" -> "block_0_end" [label="frame"];
23+
"block_0_2" [shape=rectangle, label="[2] PULSE 0 1 \"cz\" test(duration: 1e-6)"];
24+
"block_0_2" -> "block_0_end" [label="frame"];
25+
"block_0_end" [shape=circle, label="end"];
26+
}
27+
}
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
source: src/program/graphviz_dot.rs
3+
expression: dot_format
4+
---
5+
digraph {
6+
entry -> "block_0_start";
7+
entry [label="Entry Point"];
8+
subgraph cluster_0 {
9+
label="block_0";
10+
node [style="filled"];
11+
"block_0_start" [shape=circle, label="start"];
12+
"block_0_start" -> "block_0_0" [label="frame"];
13+
"block_0_start" -> "block_0_1" [label="frame"];
14+
"block_0_start" -> "block_0_2" [label="frame"];
15+
"block_0_start" -> "block_0_end" [label="frame
16+
ordering"];
17+
"block_0_0" [shape=rectangle, label="[0] NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1000000.0)"];
18+
"block_0_0" -> "block_0_1" [label="frame"];
19+
"block_0_0" -> "block_0_2" [label="frame"];
20+
"block_0_0" -> "block_0_end" [label="frame"];
21+
"block_0_1" [shape=rectangle, label="[1] PULSE 0 \"rf\" test(duration: 1000000.0)"];
22+
"block_0_1" -> "block_0_2" [label="frame"];
23+
"block_0_1" -> "block_0_end" [label="frame"];
24+
"block_0_2" [shape=rectangle, label="[2] PULSE 0 \"ro_rx\" test(duration: 1000000.0)"];
25+
"block_0_2" -> "block_0_end" [label="frame"];
26+
"block_0_end" [shape=circle, label="end"];
27+
}
28+
}
29+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
source: src/program/graphviz_dot.rs
3+
expression: dot_format
4+
---
5+
digraph {
6+
entry -> "block_0_start";
7+
entry [label="Entry Point"];
8+
subgraph cluster_0 {
9+
label="block_0";
10+
node [style="filled"];
11+
"block_0_start" [shape=circle, label="start"];
12+
"block_0_start" -> "block_0_0" [label="frame"];
13+
"block_0_start" -> "block_0_1" [label="frame"];
14+
"block_0_start" -> "block_0_3" [label="frame"];
15+
"block_0_start" -> "block_0_end" [label="ordering"];
16+
"block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
17+
"block_0_0" -> "block_0_1" [label="frame"];
18+
"block_0_0" -> "block_0_2" [label="frame"];
19+
"block_0_0" -> "block_0_3" [label="frame"];
20+
"block_0_1" [shape=rectangle, label="[1] NONBLOCKING PULSE 0 \"ro_tx\" test(duration: 1000000.0)"];
21+
"block_0_1" -> "block_0_2" [label="frame"];
22+
"block_0_1" -> "block_0_3" [label="frame"];
23+
"block_0_2" [shape=rectangle, label="[2] PULSE 0 \"rf\" test(duration: 1000000.0)"];
24+
"block_0_2" -> "block_0_3" [label="frame"];
25+
"block_0_3" [shape=rectangle, label="[3] FENCE 0"];
26+
"block_0_3" -> "block_0_4" [label="frame"];
27+
"block_0_4" [shape=rectangle, label="[4] FENCE 0"];
28+
"block_0_4" -> "block_0_end" [label="frame"];
29+
"block_0_end" [shape=circle, label="end"];
30+
}
31+
}
32+

src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__chained_pulses.snap

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@ digraph {
1010
node [style="filled"];
1111
"block_0_start" [shape=circle, label="start"];
1212
"block_0_start" -> "block_0_0" [label="frame"];
13-
"block_0_start" -> "block_0_end" [label="ordering"];
13+
"block_0_start" -> "block_0_end" [label="frame
14+
ordering"];
1415
"block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
1516
"block_0_0" -> "block_0_1" [label="frame"];
17+
"block_0_0" -> "block_0_end" [label="frame"];
1618
"block_0_1" [shape=rectangle, label="[1] PULSE 0 \"rf\" test(duration: 1000000.0)"];
1719
"block_0_1" -> "block_0_2" [label="frame"];
20+
"block_0_1" -> "block_0_end" [label="frame"];
1821
"block_0_2" [shape=rectangle, label="[2] PULSE 0 \"rf\" test(duration: 1000000.0)"];
1922
"block_0_2" -> "block_0_3" [label="frame"];
23+
"block_0_2" -> "block_0_end" [label="frame"];
2024
"block_0_3" [shape=rectangle, label="[3] PULSE 0 \"rf\" test(duration: 1000000.0)"];
2125
"block_0_3" -> "block_0_4" [label="frame"];
26+
"block_0_3" -> "block_0_end" [label="frame"];
2227
"block_0_4" [shape=rectangle, label="[4] PULSE 0 \"rf\" test(duration: 1000000.0)"];
2328
"block_0_4" -> "block_0_end" [label="frame"];
2429
"block_0_end" [shape=circle, label="end"];

src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__different_frames_blocking.snap

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ digraph {
1212
"block_0_start" -> "block_0_0" [label="frame"];
1313
"block_0_start" -> "block_0_1" [label="frame"];
1414
"block_0_start" -> "block_0_2" [label="frame"];
15-
"block_0_start" -> "block_0_end" [label="ordering"];
15+
"block_0_start" -> "block_0_end" [label="frame
16+
ordering"];
1617
"block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
1718
"block_0_0" -> "block_0_end" [label="frame"];
1819
"block_0_1" [shape=rectangle, label="[1] PULSE 1 \"rf\" test(duration: 1000000.0)"];

src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__jump.snap

+6-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ digraph {
1010
node [style="filled"];
1111
"first-block_start" [shape=circle, label="start"];
1212
"first-block_start" -> "first-block_0" [label="frame"];
13-
"first-block_start" -> "first-block_end" [label="ordering"];
13+
"first-block_start" -> "first-block_end" [label="frame
14+
ordering"];
1415
"first-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
1516
"first-block_0" -> "first-block_end" [label="frame"];
1617
"first-block_end" [shape=circle, label="end"];
@@ -22,7 +23,8 @@ digraph {
2223
node [style="filled"];
2324
"second-block_start" [shape=circle, label="start"];
2425
"second-block_start" -> "second-block_0" [label="frame"];
25-
"second-block_start" -> "second-block_end" [label="ordering"];
26+
"second-block_start" -> "second-block_end" [label="frame
27+
ordering"];
2628
"second-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
2729
"second-block_0" -> "second-block_end" [label="frame"];
2830
"second-block_end" [shape=circle, label="end"];
@@ -33,7 +35,8 @@ digraph {
3335
node [style="filled"];
3436
"third-block_start" [shape=circle, label="start"];
3537
"third-block_start" -> "third-block_0" [label="frame"];
36-
"third-block_start" -> "third-block_end" [label="ordering"];
38+
"third-block_start" -> "third-block_end" [label="frame
39+
ordering"];
3740
"third-block_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(duration: 1000000.0)"];
3841
"third-block_0" -> "third-block_end" [label="frame"];
3942
"third-block_end" [shape=circle, label="end"];

src/program/snapshots/quil_rs__program__graphviz_dot__tests__graph__parametric_pulse.snap

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ digraph {
1010
node [style="filled"];
1111
"block_0_start" [shape=circle, label="start"];
1212
"block_0_start" -> "block_0_0" [label="frame"];
13-
"block_0_start" -> "block_0_end" [label="ordering"];
13+
"block_0_start" -> "block_0_1" [label="frame"];
14+
"block_0_start" -> "block_0_end" [label="frame
15+
ordering"];
1416
"block_0_0" [shape=rectangle, label="[0] PULSE 0 \"rf\" test(a: param[0])"];
1517
"block_0_0" -> "block_0_1" [label="frame"];
16-
"block_0_0" -> "block_0_end" [label="await read"];
18+
"block_0_0" -> "block_0_end" [label="await read
19+
frame"];
1720
"block_0_1" [shape=rectangle, label="[1] CAPTURE 0 \"ro_rx\" test(a: param[0]) ro[0]"];
1821
"block_0_1" -> "block_0_end" [label="await capture
1922
await read

0 commit comments

Comments
 (0)