Skip to content

New parallel expression in Q##3298

Open
swernli wants to merge 10 commits into
mainfrom
swernli/parallel
Open

New parallel expression in Q##3298
swernli wants to merge 10 commits into
mainfrom
swernli/parallel

Conversation

@swernli

@swernli swernli commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

This change adds support for a new parallel expression that alters the behavior of qubit allocation and code generation. There are two forms of the expression:

  • parallel <expr>: For the duration of the expression after the keyword, all qubit release is deferred such that each qubit allocation uses a fresh qubit identifier. In addition, when configured for QIR code generation, no expressions that result in dynamic branching are allowed within the expression and all loops within the expression are unrolled. This ensures that the generated QIR for that expression will be contained within a single LLVM block. Combining these two features (defferred release and enforced single block) maximizs the likelihood that the code corresponding to the expression will be parallelized by the target's compilation and scheduling.
  • parallel within <limit> <expr>: This form has the same semantics as the first form, but with the added integer limit that expresses how many fresh qubit allocations will be used before normal qubit reuse begins. For example, parallel wthin 4 <expr> will defer the release of four qubits, and if a fifth allocation is made within the expression then that allocation will draw from the previously deferred qubits, returning to a reuse pattern. This allows the program to express a limited amount of parallelism while still allowing for some reuse, providing more fine grained control over the width/depth (space/time) tradeoff of the algorithm. The limit expression must be a static (compile-time constant) integer.

The new expression affects the behavior of QIR code generation, simulation, and resource estimation, and the change includes updated parser, AST, HIR, FIR, and lowering logic for the syntax as well as new RCA checks to enforce the requirements for control flow structure and static integer limits.

Fixes #3274

swernli and others added 4 commits June 8, 2026 12:54
This change adds support for a new `parallel` expression that alters the behavior of qubit allocation and code generation. There are two forms of the expression:
 - `parallel <expr>`: For the duration of the expression after the keyword, all qubit release is deferred such that each qubit allocation uses a fresh qubit identifier. In addition, when configured for QIR code generation, no expression that result in dynamic branching are allowed within the expression and all loops within the expression are unrolled. This ensures that the generated QIR for that expression will be contained within a single LLVM block. Combining these two features (defferred release and enforced single block) maximizs the likelihood that the code corresponding to the expression will be parallelized by the target's compilation and scheduling.
 - `parallel within <limit> <expr>`: This form has the same semantics as the first form, but with the added integer limit that expresses how many fresh qubit allocations will be used before normal qubit reuse begins. For example, `parallel wthin 4 <expr>` will defer the release of four qubits, and if a fifth allocation is made within the expression the previously deffered qubits will be made available for reuse. This allows the program to express a limited amount of parallelism while still allowing for some reuse, providing more fine grained control over the width/depth (space/time) tradeoff of the algorithm. The limit expression must be a static (compile-time constant) integer.

The new expression affects the behavior of QIR code generation, simulation, and resource estimation, and the change includes updated parser, AST, HIR, FIR, and lowering logic for the syntax as well as new RCA checks to enforce the requirements for control flow structure and static integer limits.
Add comprehensive tests covering the new parallel expression syntax:

- Parser tests: 11 new tests in qsc_parse covering parallel block syntax,
  parallel within with static/dynamic limits, nested parallel, and
  disambiguation from within..apply syntax
- RCA tests: 12 new tests in qsc_rca/tests/parallel.rs verifying compute
  property analysis for static, dynamic, and branching parallel bodies,
  dynamic limits, nested parallel, and indirect branching via callables
- Capabilities check tests: 5 new test sources in tests_common.rs with
  corresponding tests in all 4 profile test files (base, adaptive,
  adaptive+integers, adaptive+integers+floats)
- Eval simulation tests: 7 new tests in qsc_eval verifying qubit ID
  recycling/deferral behavior for baseline, parallel, nested parallel,
  and parallel within expressions
- Circuit tests: 7 new tests in qsc circuit_tests.rs verifying qubit wire
  allocation behavior mirrors the eval simulation results

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds tests/parallel.rs to the qsc_partial_eval crate covering:
- Baseline qubit ID recycling without parallel
- Deferred qubit release inside parallel
- Qubit ID reuse after parallel block ends
- Nested parallel deferring inner releases to outer
- parallel within N reusing IDs after limit
- Nested parallel within propagating through outer limit
- Unlimited outer parallel deferring all releases
- Parallel forcing loop unrolling even with AdaptiveRIFLA profile

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CircuitEntryPoint::EntryPoint,
);

expect![[r#"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is difficult for me to understand what should be expected from the nested parallel within expressions, so I'll double-check with you. Is this correct and what is expected with the nested setup?

/// A parallel expression: `parallel a`
Parallel(Box<Expr>),
/// A parallel-limited expression: `parallel within n a`
ParallelLimited(Box<Expr>, Box<Expr>),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh I didn't consider that we could have expressions for the limit of the parallel-within. Does that mean that the parallel limit could be determined by the result of a measurement?

use q0 = Qubit();
H(q0);
mutable limit = 2;
if MResetZ(q0) == One {
    limit = 4;
}
parallel within limit {
    { use q1 = Qubit(); H(q1); }
    { use q2 = Qubit(); H(q2); }
    { use q3 = Qubit(); H(q3); }
    { use q4 = Qubit(); H(q4); }
}

ExprKind::Lit(lit) => write!(indent, "Lit: {lit}")?,
ExprKind::Parallel(e) => write!(indent, "Parallel: {e}")?,
ExprKind::ParallelLimited(limit, body) => {
write!(indent, "ParallelLimited: {limit} {body}")?;

@ScottCarda-MS ScottCarda-MS Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since limit and body can be full expressions, we should probably print them on their own lines. Perhaps, if the limit is usually expected to be a literal, we could print it as "ParallelLimited({limit}):
{body}
"
Otherwise
I might suggest

"ParallelLimited:
    {limit}
    {body}
"

similar to what Conjugate does.

ExprKind::Lit(lit) => write!(indent, "Lit: {lit}")?,
ExprKind::Parallel(limit, expr) => {
if let Some(limit) = limit {
write!(indent, "Parallel({limit}): {expr}")?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah I see you went with the parenthesized limit for the HIR display. I found that that gets pretty tricky to read when I put anything more than a straight int literal in for the limit. I'd still recommend to put the limit and the body on their own separate lines underneath. Vertical length is easier to handle than horizontal length in those displays.

@ScottCarda-MS

Copy link
Copy Markdown
Contributor

Because the limit of the parallel within <limit> <body> is also a full expression, it can become a block with side-effects and have its own qubit allocation and even sub-parallel expressions. We should be sure to test some of these scenarios for correct behavior.
For example, we should test

  • a parallel within expression doesn't take effect in its own limit expression, only its body
  • a surrounding parallel or parallel within does affect the limit expression of a parallel within contained inside of the surrounding parallel or parallel within's body expression.
    i.e
parallel within X {
  parallel within {
    // Should be subject to `parallel within X`
    use q0 = Qubit();
    use q1 = Qubit();
    Y
  } {...}
}
  • parallel within expressions are effective inside the a block expression used for the limit expression of a surrounding parallel within, and that they don't affect the surrounding parallel within's body expression

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design for parallel keyword in Q#

2 participants