Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: track memory dependencies involving the block terminator #433

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

antalsz
Copy link
Contributor

@antalsz antalsz commented Jan 10, 2025

This PR adds the block terminator instruction to the analysis done by ScheduledBasicBlock::build, so that we correctly track its memory references when it's a conditional jump. This instruction is considered to live at the "index" ScheduledGraphNode::BlockEnd.

Concretely, this PR is for programs like the following (this is the test program::scheduling::graph::graphviz_dot_tests::memory_dependency_in_block_terminator):

DECLARE ro BIT

NONBLOCKING CAPTURE 0 "ro_rx" flat(duration: 2.0000000000000003e-06, iq: 1.0, scale: 1.0, phase: 0.8745492960861506, detuning: 0.0) ro
JUMP-WHEN @eq ro
LABEL @eq
PULSE 0 "ro_tx" gaussian(duration: 1, fwhm: 2, t0: 3)

There ought to be an "await capture" edge from the NONBLOCKING CAPTURE to the JUMP-WHEN.

Previously, this program generated the following incorrect scheduled dependency graph, with no "await capture" edges at all (generated from DOT with dot -Tpng -Gdpi=300 in.dot > out.png):

Old, incorrect scheduled dependency graph

Now, it generates this more correct scheduled dependency graph, where the edge from the NONBLOCKING CAPTURE to the block end is labeled with "await capture" as well as "ordering" and "timing":

New, correct scheduled dependency graph

The diff between the DOT of the two graphs is

     "block_0_start" -> "block_0_0" [label="ordering
 timing"];
     "block_0_0" [shape=rectangle, label="[0] NONBLOCKING CAPTURE 0 \"ro_rx\" flat(detuning: 0, duration: 2.0000000000000003e-6, iq: 1, phase: 0.8745492960861506, scale: 1) ro[0]"];
-    "block_0_0" -> "block_0_end" [label="ordering
+    "block_0_0" -> "block_0_end" [label="await capture
+ordering
 timing"];
     "block_0_end" [shape=circle, label="end"];

The full DOT for the old, incorrect graph is:

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
timing"];
    "block_0_0" [shape=rectangle, label="[0] NONBLOCKING CAPTURE 0 \"ro_rx\" flat(detuning: 0, duration: 2.0000000000000003e-6, iq: 1, phase: 0.8745492960861506, scale: 1) ro[0]"];
    "block_0_0" -> "block_0_end" [label="ordering
timing"];
    "block_0_end" [shape=circle, label="end"];
  }
  "block_0_end" -> "@eq_start" [label="if ro[0] != 0"];
  "block_0_end" -> "@eq_start" [label="if ro[0] == 0"];
  subgraph cluster_1 {
    label="@eq";
    node [style="filled"];
    "@eq_start" [shape=circle, label="start"];
    "@eq_start" -> "@eq_0" [label="ordering
timing"];
    "@eq_start" -> "@eq_end" [label="ordering
timing"];
    "@eq_0" [shape=rectangle, label="[0] PULSE 0 \"ro_tx\" gaussian(duration: 1, fwhm: 2, t0: 3)"];
    "@eq_0" -> "@eq_end" [label="ordering
timing"];
    "@eq_end" [shape=circle, label="end"];
  }
}

The full DOT for the new graph is:

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
timing"];
    "block_0_0" [shape=rectangle, label="[0] NONBLOCKING CAPTURE 0 \"ro_rx\" flat(detuning: 0, duration: 2.0000000000000003e-6, iq: 1, phase: 0.8745492960861506, scale: 1) ro[0]"];
    "block_0_0" -> "block_0_end" [label="await capture
ordering
timing"];
    "block_0_end" [shape=circle, label="end"];
  }
  "block_0_end" -> "@eq_start" [label="if ro[0] != 0"];
  "block_0_end" -> "@eq_start" [label="if ro[0] == 0"];
  subgraph cluster_1 {
    label="@eq";
    node [style="filled"];
    "@eq_start" [shape=circle, label="start"];
    "@eq_start" -> "@eq_0" [label="ordering
timing"];
    "@eq_start" -> "@eq_end" [label="ordering
timing"];
    "@eq_0" [shape=rectangle, label="[0] PULSE 0 \"ro_tx\" gaussian(duration: 1, fwhm: 2, t0: 3)"];
    "@eq_0" -> "@eq_end" [label="ordering
timing"];
    "@eq_end" [shape=circle, label="end"];
  }
}

Closes #429

@antalsz antalsz added the bug Something isn't working label Jan 10, 2025
@antalsz antalsz requested review from Shadow53 and kalzoo January 10, 2025 17:46
@antalsz antalsz self-assigned this Jan 10, 2025
Copy link

github-actions bot commented Jan 10, 2025

PR Preview Action v1.6.0

🚀 View preview at
https://rigetti.github.io/quil-rs/pr-preview/pr-433/

Built to branch quil-py-docs at 2025-01-10 17:50 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

#[error(
"Error scheduling {}: {}: {variant:?}",
match .instruction_node {
None => "an instruction".to_string(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should just implement Display here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe? I'm always a bit reluctant to implement Display for fragments like this that wouldn't make sense in every sentence

@@ -306,17 +316,28 @@ impl<'a> ScheduledBasicBlock<'a> {

let extern_signature_map = ExternSignatureMap::try_from(program.extern_pragma_map.clone())
.map_err(|(pragma, _)| ScheduleError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't hold you to this, but I probably should have made ScheduleError into an enum here; this variant would include the pragma rather than a None for instruction_node. That involves making downstream accommodations.

DECLARE depends_on_ro BIT
NONBLOCKING CAPTURE 0 "ro_rx" flat(duration: 2.0000000000000003e-06, iq: 1.0, scale: 1.0, phase: 0.8745492960861506, detuning: 0.0) ro
MOVE depends_on_ro ro
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here if depends_on_ro is used in the next block? Is that supported? I would make a test case for that, regardless as to whether it's supported. If not supported, a failing test case, issue, and some documentation on the limitation would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only track dependencies within blocks because there's already a total ordering constraint between blocks; we don't do inter-block scheduling. (This is, AFAIK, already documented.) So it's not supported but because it's not really relevant. A test case is a fine idea, but I don't think it will fail and don't think there's an issue to create.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory dependencies do not consider the block terminators
3 participants