You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
qsc catches integer division by zero and out-of-range array indices at compile time when the operands are static. When the operand depends on a measurement result, the Adaptive-family profiles emit the unchecked instruction — no diagnostic, no runtime check, and no path to a non-zero exit code. The same program raises under simulation and silently continues when compiled. This is not a conformance claim (the spec contemplates such errors as UB); it is that one source means two different things depending on targetProfile, and that the emitted IR has no instruction that could report the failure at all.
Repro A — division by zero (Adaptive_RI, Adaptive_RIF, Adaptive)
namespaceT {
@EntryPoint()
operationMain() : Int {
useq=Qubit();
X(q); // deterministic: M(q) is always Oneletd=M(q) ==One?0|1; // divisor is always 0return100/d;
}
}
Unrestricted simulator: raises Error: division by zero, 20/20 shots. All three Adaptive-family profiles compile clean, with zero comparison (icmp) instructions in the module and an unchecked sdiv whose divisor is always 0, ending in ret i64 0. On Adaptive_RI / Adaptive_RIF (QIR v1):
block_3:
%var_8 = phii64 [0, %block_1], [1, %block_2]
%var_6 = sdivi64100, %var_8; %var_8 is 0 on every shotcallvoid@__quantum__rt__int_record_output(i64%var_6, i8* ...)
reti640
Plain Adaptive (QIR v2) lowers the divisor via alloca/load rather than a phi, but is otherwise the same — unchecked sdiv i64 100, <load>, zero icmp, ret i64 0. A static divisor (100 / 0) is caught on all three: Qsc.PartialEval.EvaluationFailed: division by zero. X(q) is used so the divisor is deterministically 0 and no sampling argument is needed; H(q) makes it probabilistic with the same result.
Repro B — array index (Adaptive only)
The docs promise this one unconditionally (item access): "An exception is thrown at runtime if the index … is outside the bounds of the array." The simulator does; Adaptive does not.
namespaceT {
@EntryPoint()
operationMain() : Int {
lettable= [100, 200, 300];
useq=Qubit[2];
H(q[0]); H(q[1]);
leta=M(q[0]) ==One?1|0;
letb=M(q[1]) ==One?2|0;
returntable[a+b]; // index in {0,1,2,3}; 3 is out of bounds
}
}
Adaptive_RI/Adaptive_RIF reject this with Qsc.CapabilitiesCk.UseOfDynamicIndex. Adaptive permits dynamic indices by design (module flags !"arrays" i1 true, !"backwards_branching" i2 3) — that is intended; the gap is that permitting them comes with no check. A static out-of-range index is caught on all three. (SSA names above are illustrative and depend on the exact source.)
Why these are the same bug
Both follow the same shape in qsc_partial_eval: reject only the statically-provable case, emit unchecked otherwise.
lib.rs:3213 (comment at 3212) — literal only:
// Validate that the RHS is not a zero.ifletOperand::Literal(Literal::Integer(0)) = rhs_operand {let error = EvalError::DivZero(bin_op_expr_span).into();returnErr(error);}
lib.rs:5094 — only when the array is empty:
if array.is_empty(){// Even though we don't know what the index value is, we know any index into an empty array is out of range,// so just return an error with index 0 here.
lib.rs:507 — the exit code is a hardcoded literal:
// Encode finalize as `Return(Some(Integer(0)))` so the QIR v2 renderer emits// the entry-point convention as `ret i64 0` through the value-return path.
And qsc_rir's Instruction enum (rir.rs:377) has no Fail/Abort/Trap variant — so a compiled program has no instruction that could report a runtime failure, whatever happens. The division and array cases are two symptoms of that one structural gap.
Expected
Any one of these, cheapest first:
A compile-time diagnostic when a measurement-dependent value reaches a fallible classical operation — the RCA machinery already does exactly this for UseOfDynamicIndex.
An emitted runtime check producing a non-zero exit code. The spec provides the mechanism and assigns it to the compiler: "Injecting suitable logic to produce a non-zero exit code … is generally up to the compiler, and the backend is not required to detect runtime failures"; "The program IR must use exit codes within the range 1 to 63 to indicate a failure."
At minimum, publish the limitation. The v1.30.0 notes point to the wiki QIR page for the Adaptive profile's "latest capabilities, limitations, and known issues", but that page has only a Runtime Capabilities Check error reference — no limitations section. Repro A is not specific to the in-progress Adaptive profile: it reproduces on Adaptive_RI/Adaptive_RIF too, and the literal-only Div check dates to Partial evaluation support for binary operations #1499 (2024-05-13). Unchanged on main as of ebe6090.
Not claiming a conformance violation
The spec contemplates real-time classical errors as UB — "An Adaptive Profile program that undergoes a real-time classical error (for example unchecked division by zero) has undefined behavior." The point is narrower: the same Q# source silently means two different things depending on targetProfile, the compiled path drops a check the simulator performs, and the IR cannot express the failure — and none of this is documented.
Environment
qdk 1.30.0 / qsharp 1.30.0 (unchanged on main as of ebe6090), Python 3.12, Windows
Summary
qsccatches integer division by zero and out-of-range array indices at compile time when the operands are static. When the operand depends on a measurement result, the Adaptive-family profiles emit the unchecked instruction — no diagnostic, no runtime check, and no path to a non-zero exit code. The same program raises under simulation and silently continues when compiled. This is not a conformance claim (the spec contemplates such errors as UB); it is that one source means two different things depending ontargetProfile, and that the emitted IR has no instruction that could report the failure at all.Repro A — division by zero (
Adaptive_RI,Adaptive_RIF,Adaptive)Unrestrictedsimulator: raisesError: division by zero, 20/20 shots. All three Adaptive-family profiles compile clean, with zero comparison (icmp) instructions in the module and an uncheckedsdivwhose divisor is always 0, ending inret i64 0. OnAdaptive_RI/Adaptive_RIF(QIR v1):Plain
Adaptive(QIR v2) lowers the divisor viaalloca/loadrather than aphi, but is otherwise the same — uncheckedsdiv i64 100, <load>, zeroicmp,ret i64 0. A static divisor (100 / 0) is caught on all three:Qsc.PartialEval.EvaluationFailed: division by zero.X(q)is used so the divisor is deterministically 0 and no sampling argument is needed;H(q)makes it probabilistic with the same result.Repro B — array index (
Adaptiveonly)The docs promise this one unconditionally (item access): "An exception is thrown at runtime if the index … is outside the bounds of the array." The simulator does;
Adaptivedoes not.Adaptive_RI/Adaptive_RIFreject this withQsc.CapabilitiesCk.UseOfDynamicIndex.Adaptivepermits dynamic indices by design (module flags!"arrays" i1 true,!"backwards_branching" i2 3) — that is intended; the gap is that permitting them comes with no check. A static out-of-range index is caught on all three. (SSA names above are illustrative and depend on the exact source.)Why these are the same bug
Both follow the same shape in
qsc_partial_eval: reject only the statically-provable case, emit unchecked otherwise.lib.rs:3213(comment at 3212) — literal only:lib.rs:5094— only when the array is empty:lib.rs:507— the exit code is a hardcoded literal:And
qsc_rir'sInstructionenum (rir.rs:377) has noFail/Abort/Trapvariant — so a compiled program has no instruction that could report a runtime failure, whatever happens. The division and array cases are two symptoms of that one structural gap.Expected
Any one of these, cheapest first:
UseOfDynamicIndex.1to63to indicate a failure."Adaptiveprofile: it reproduces onAdaptive_RI/Adaptive_RIFtoo, and the literal-only Div check dates to Partial evaluation support for binary operations #1499 (2024-05-13). Unchanged onmainas ofebe6090.Not claiming a conformance violation
The spec contemplates real-time classical errors as UB — "An Adaptive Profile program that undergoes a real-time classical error (for example unchecked division by zero) has undefined behavior." The point is narrower: the same Q# source silently means two different things depending on
targetProfile, the compiled path drops a check the simulator performs, and the IR cannot express the failure — and none of this is documented.Environment
qdk1.30.0 /qsharp1.30.0 (unchanged onmainas ofebe6090), Python 3.12, Windows