Repository navigation
Description
On Adaptive, an early return inside a for loop over a range, with a qubit array in scope,
panics with "qubit should still be alive". Adaptive_RI and Adaptive_RIF compile both programs
below to the right result.
How this differs from #3940. #3940 has the return under an if inside the loop; the
qubit releases on the returning if arm leak into the other arm because partial evaluation
did not restore qubit state at the if join. #3943 fixes that by saving and merging qubit
state at if joins, and #3940's programs compile with it. The two programs here are not fixed
by #3943 (checked on its head abe8816), nor by #4032 or #4033:
return_in_loop_body: areturndirectly in the loop body, with noif. After the
early-return lowering, the loop condition depends on the return flag, so partial evaluation
emits the loop into the QIR (eval_expr_emit_whileinqsc_partial_eval/src/lib.rs). It
evaluates the body once and keeps the qubit state it ends with, including the release of
qson the return path; the release after the loop then finds the qubits already gone.
There is noifjoin, so Fix qubit state across dynamic branches #3943's save/merge is never reached.taken_return_then_alloc: [Panic] qsc_eval: Adaptive, earlyreturnin aforloop that indexes a qubit array #3940's shape, then a takenreturnand a new allocation.
Without Fix qubit state across dynamic branches #3943 it panics like [Panic] qsc_eval: Adaptive, earlyreturnin aforloop that indexes a qubit array #3940. With Fix qubit state across dynamic branches #3943 it panics differently, "IndexMap::index:
index out of bounds" (inqsc_partial_eval/src/management.rs), so it is a remainder that
Fix qubit state across dynamic branches #3943's merge does not handle. The cause is not pinned; the qubit state merged at the guard
of the later, statically takenreturnlooks involved. With Fix qubit state across dynamic branches #3943 and Fix runtime callable compilation and closure specialization #4032 applied, it
compiles to the right result when the laterreturnis removed or not taken, when the loop
is removed, or whenqsis a singleQubit()instead of an array.
The emitted-loop case probably also shows up as a refusal of a valid program: with an empty range
(for i in 0..-1..0 { return MResetZ(qs[2]); }) and a later MResetZ(qs[1]), Adaptive
reports "qubit used after release".
Reproduces on main (94b1ef2), #3943 (abe8816), #4032 (99464f3, 8de8fa2) and #4033
(64acdf2); Adaptive only.
"""
Adaptive: an early `return` in a `for` loop over a range, with a qubit array
in scope, panics with "qubit should still be alive" (G1), and a variant with a
later taken `return` and a new allocation still panics with #3943 applied (G3).
Exits 1 while either panic reproduces on Adaptive, and 0 once it no longer does.
"""
import sys
from qdk import qsharp
PROGRAMS = {
# A `return` directly in the loop body.
"return_in_loop_body": """
operation Main() : Int {
use qs = Qubit[2];
for i in 0..1 {
return i;
}
5
}
""",
# The #3940 shape (return under an `if` in the loop), then a taken return
# and a new allocation.
"taken_return_then_alloc": """
operation Main() : Unit {
use qs = Qubit[2];
for i in 0..1 {
if i < 0 { return (); }
}
if true { return (); }
use more = Qubit[3];
}
""",
}
reproduced = False
for name, source in PROGRAMS.items():
qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted)
qsharp.eval(source)
print(f"{name}: simulator {qsharp.eval('Main()')}")
for profile in ["Adaptive_RI", "Adaptive_RIF", "Adaptive"]:
qsharp.init(target_profile=getattr(qsharp.TargetProfile, profile))
qsharp.eval(source)
try:
qsharp.compile("Main()")
print(f" {profile:13} compiles")
except BaseException as e: # a Rust panic surfaces as PanicException
panicked = type(e).__name__ == "PanicException"
reproduced |= panicked
print(f" {profile:13} {type(e).__name__}: {str(e).splitlines()[0]}")
print("REPRODUCED" if reproduced else "NOT REPRODUCED")
sys.exit(1 if reproduced else 0)Output with #3943 applied:
return_in_loop_body: simulator 0
Adaptive_RI compiles
Adaptive_RIF compiles
Adaptive PanicException: qubit should still be alive
taken_return_then_alloc: simulator None
Adaptive_RI compiles
Adaptive_RIF compiles
Adaptive PanicException: IndexMap::index: index out of bounds
REPRODUCED
On main, taken_return_then_alloc panics with "qubit should still be alive" instead.
Activity