Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

RCA: Bool/Int result of an intrinsic function (e.g. `read_loss` behind `MResetZChecked`) is treated as constant; heralded code is dropped

Open
#4,005 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
rust
Domain
compilers

Research direction

Start by tracing how RCA assigns value kinds to intrinsic function results, especially when an argument is dynamic but constant; the issue identifies __quantum__rt__read_loss, MResetZChecked, and @SimulatableIntrinsic as affected paths. Use the M3 repro to check whether the heralded X remains in QIR under Adaptive_RI and Adaptive_RIF, and compare with the listed panic cases. Done means runtime Bool and Int results stay dynamic through control flow and mutable reads across the reported profiles.

Written by the indexing model from the issue text.

Description

bug needs triage

Summary

RCA gives the output of every intrinsic function the kind Dynamic { value_kind: Constant } when an argument is dynamic but "constant", which is the kind a measured Result has. This covers __quantum__rt__read_loss, which is behind the public MResetZChecked and IsLossResult, and user @SimulatableIntrinsic functions. The returned Bool/Int is a runtime value. Because RCA does not mark it Variable, an if on it opens no dynamic scope, and a mutable set inside stays Static. Partial evaluation still branches at run time, but later folds reads of that mutable at compile time. The effects are:

  • the QIR records a constant instead of the runtime value;
  • heralded gates are removed;
  • three different panics occur in partial evaluation and RIR.

Affected profiles:

  • MResetZChecked: Adaptive_RI and Adaptive_RIF. On Adaptive the programs happen to give the right value: the mutable is kept in a stack variable and the comparison is computed at run time.
  • Direct read_loss call or a user intrinsic: Adaptive_RI, Adaptive_RIF and Adaptive.

At the end of this issue there is a question for the RCA owners about the right rule.

Minimal repro

M3, heralded correction dropped (Main()). The X(t), applied only when q was not lost, is removed from the QIR on Adaptive_RI/RIF:

import Std.Measurement.*;
operation Main() : (Bool, Result) {
    use (q, t) = (Qubit(), Qubit());
    let (_, lost) = MResetZChecked(q);
    mutable n = 0;
    if not lost { set n = 5; }
    if n == 5 { X(t); }
    (lost, MResetZ(t))
}

M1, wrong value on every shot where the qubit is not lost (Main(), Adaptive_RI/RIF):

import Std.Measurement.*;
operation Main() : (Result, Bool) {
    use q = Qubit();
    let (r, lost) = MResetZChecked(q);
    mutable y = 0;
    if lost { set y = 1; }
    (r, y == 0)
}

M4, a user intrinsic, wrong on all three profiles (Main()):

@SimulatableIntrinsic()
function IsOneExt(r : Result) : Bool { r == One }
operation Main() : Bool {
    use q = Qubit();
    X(q);
    mutable n = 0;
    if IsOneExt(MResetZ(q)) { set n = 5; }
    n == 5
}

Other variants (same cause), with the entry point used:

// M2: direct read_loss call, wrong on all three profiles; Main()
import QIR.Runtime.__quantum__rt__read_loss;
operation Main() : Bool {
    use q = Qubit();
    let lost = __quantum__rt__read_loss(MResetZ(q));
    mutable y = 0;
    if lost { set y = 1; }
    y == 0
}
// P1: panic, counting the shots that were not lost; Main()
import Std.Measurement.*;
operation Main() : Int {
    use q = Qubit();
    let (_, lost) = MResetZChecked(q);
    mutable good = 0;
    if not lost { set good += 1; }
    good + 0
}
// P2: panic, an intrinsic Int used as an array index; Main()
@SimulatableIntrinsic()
function CountExt(r : Result) : Int { r == One ? 1 | 0 }
operation Main() : Int {
    use q = Qubit();
    X(q);
    let k = CountExt(MResetZ(q));
    let arr = [10, 20, 30];
    arr[k]
}

More variants of the same family:

  • M5: an or whose left operand is a loss flag. This has the shape of #3976, but #3977 alone does not fix it (see below).
  • M6: a nested if on a negated loss flag.
  • math_dc and math_dc_dbl: the value passed through MaxI or IntAsDouble before the test.
  • P1b: a loss count in a for loop.
  • P3: the count used as a range bound.
Sources of the other variants
// M5; Main()
import QIR.Runtime.__quantum__rt__read_loss;
operation Main() : (Int, Bool) {
    use q = Qubit();
    let lost = __quantum__rt__read_loss(MResetZ(q));
    mutable n = 0;
    let _ = lost or { set n = 6; true };
    (n, n == 6)
}
// M6; Prog(true)
import Std.Measurement.*;
import QIR.Runtime.__quantum__rt__read_loss;
operation Prog(x0 : Bool) : (Result, Int, Bool) {
    use (q0, q1) = (Qubit(), Qubit());
    if x0 { X(q0); }
    let r = MResetZ(q0);
    mutable zo = -1;
    mutable bo = false;
    if r == One {
        let x = not __quantum__rt__read_loss(MResetZ(q1));
        mutable z = 0;
        if x { set z = 1; }
        set zo = z;
        set bo = z == 1;
    }
    (r, zo, bo)
}
// math_dc; Main()  (math_dc_dbl: `let d = IntAsDouble(CountExt(MResetZ(q)));` and `if d > 0.5`)
import Std.Math.*;
import Std.Convert.*;
@SimulatableIntrinsic()
function CountExt(r : Result) : Int { r == One ? 1 | 0 }
operation Main() : Bool {
    use q = Qubit();
    X(q);
    let k = CountExt(MResetZ(q));
    let m = MaxI(k, 0);
    mutable n = 0;
    if m == 1 { set n = 5; }
    n == 5
}
// P1b; Main()
import Std.Measurement.*;
operation Main() : Int {
    use qs = Qubit[2];
    mutable good = 0;
    for q in qs {
        let (_, lost) = MResetZChecked(q);
        if not lost { set good += 1; }
    }
    good
}
// P3; Main()
import Std.Measurement.*;
operation Main() : (Result, Result) {
    use (q, t) = (Qubit(), Qubit());
    X(q);
    let (r, lost) = MResetZChecked(q);
    mutable n = 0;
    if not lost { set n = 1; }
    for _ in 1..n { X(t); }
    (r, MResetZ(t))
}

The script is a variant of check.py from #4001 (python check_new3.py prog.qs "Main()"; needs pip install qdk qirrunner). qir-runner 0.9.7 cannot link __quantum__rt__read_loss or user intrinsics, so the script gives them bodies before running the QIR:

  • read_loss returns false, meaning no loss, as in the simulator.
  • IsOneExt and CountExt read the measured result.

The wrong answers do not depend on these stubs: for M1, M2 and M4 the QIR records the constant false directly (shown after the table).

check_new3.py
import re
import sys
import qirrunner
from qdk import Context, TargetProfile

# qir-runner 0.9.7 cannot link __quantum__rt__read_loss or user intrinsics, so
# give them bodies before running: read_loss returns false (no loss, as in the
# simulator); the test intrinsics IsOneExt/CountExt read the measured result.
STUBS = {
    "__quantum__rt__read_loss": "ret i1 false",
    "IsOneExt": "%v = call i1 @__quantum__qis__read_result__body(PTR %a0)\n  ret i1 %v",
    "CountExt": "%v = call i1 @__quantum__qis__read_result__body(PTR %a0)\n"
    "  %w = zext i1 %v to i64\n  ret i64 %w",
}


def stub(qir):
    ptr = "%Result*" if "%Result*" in qir else "ptr"
    for name, body in STUBS.items():
        m = re.search(rf"^declare (?:zeroext )?(i1|i64) @{name}\(([^)]*)\).*$", qir, re.M)
        if m:
            fn = f"define {m[1]} @{name}({m[2]} %a0) {{\n  {body.replace('PTR', ptr)}\n}}"
            qir = qir[: m.start()] + fn + qir[m.end() :]
            if "read_result" in body and "declare i1 @__quantum__qis__read_result" not in qir:
                qir += f"\ndeclare i1 @__quantum__qis__read_result__body({ptr})\n"
    return qir


src, entry = open(sys.argv[1]).read(), sys.argv[2]
sim = Context(target_profile=TargetProfile.Unrestricted)
sim.eval(src)
print("simulator:", sim.run(entry, 1)[0])
for p in ["Adaptive_RI", "Adaptive_RIF", "Adaptive"]:
    try:
        ctx = Context(target_profile=getattr(TargetProfile, p))
        ctx.eval(src)
        qir = str(ctx.compile(entry))
    except BaseException as e:  # panics surface as PanicException
        print(f"{p}: {type(e).__name__}: {str(e).splitlines()[0]}")
        continue
    x_gates = len(re.findall(r"call void @__quantum__qis__x__body", qir))
    with open("prog.ll", "w") as f:
        f.write(stub(qir))
    out = qirrunner.OutputHandler()
    try:
        qirrunner.run("prog.ll", shots=1, output_fn=out.handle)
    except BaseException as e:
        print(f"{p}: qir-runner error: {str(e).splitlines()[0]}")
        continue
    lines = [l for l in out.get_output().splitlines() if l.startswith("OUTPUT")]
    print(f"{p}:", " ".join(lines), f"[x__body calls in QIR: {x_gates}]")

Expected vs actual

On main 1b8de0d17 (identical, line for line, on the earlier main commits a90b8f777 and 48449939c and on open PR #3902 577dc9382):

Program Simulator (expected) Adaptive_RI / RIF Adaptive
M3 heralded X (main repro) (false, One) (false, Zero), no X in QIR (false, One) (correct)
M1 MResetZChecked (Zero, true) (Zero, false) (Zero, true) (correct)
M4 user intrinsic true false false
M2 direct read_loss true false false
M5 or with loss left operand (6, true) (6, false) (6, false)
M6 nested, Prog(true) (One, 1, true) (One, 1, false) (One, 1, false)
MaxI / IntAsDouble true false false
P1 / P1b count of non-lost 1 / 2 panic value is not addable: Var 1 / 2 (correct)
P2 intrinsic Int as index 20 panic Unexpected advanced instruction at BlockId(0), instruction 4 20 (correct)
P3 count as range bound (One, One) panic value should be Int, got Var UseOfDynamicRange (rejected)
Script output (qir-runner 0.9.7, 1 shot each)
### M3 Main()
simulator: (False, One)
Adaptive_RI: OUTPUT	TUPLE	2	0_t OUTPUT	BOOL	false	1_t0b OUTPUT	RESULT	0	2_t1r [x__body calls in QIR: 0]
Adaptive_RIF: OUTPUT	TUPLE	2	0_t OUTPUT	BOOL	false	1_t0b OUTPUT	RESULT	0	2_t1r [x__body calls in QIR: 0]
Adaptive: OUTPUT	TUPLE	2	0_t OUTPUT	BOOL	false	1_t0b OUTPUT	RESULT	1	2_t1r [x__body calls in QIR: 1]
### M1 Main()
simulator: (Zero, True)
Adaptive_RI: OUTPUT	TUPLE	2	0_t OUTPUT	RESULT	0	1_t0r OUTPUT	BOOL	false	2_t1b [x__body calls in QIR: 0]
Adaptive_RIF: OUTPUT	TUPLE	2	0_t OUTPUT	RESULT	0	1_t0r OUTPUT	BOOL	false	2_t1b [x__body calls in QIR: 0]
Adaptive: OUTPUT	TUPLE	2	0_t OUTPUT	RESULT	0	1_t0r OUTPUT	BOOL	true	2_t1b [x__body calls in QIR: 0]
### M2 Main()
simulator: True
Adaptive_RI: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 0]
Adaptive_RIF: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 0]
Adaptive: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 0]
### M4 Main()
simulator: True
Adaptive_RI: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive_RIF: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
### M5 Main()
simulator: (6, True)
Adaptive_RI: OUTPUT	TUPLE	2	0_t OUTPUT	INT	6	1_t0i OUTPUT	BOOL	false	2_t1b [x__body calls in QIR: 0]
Adaptive_RIF: OUTPUT	TUPLE	2	0_t OUTPUT	INT	6	1_t0i OUTPUT	BOOL	false	2_t1b [x__body calls in QIR: 0]
Adaptive: OUTPUT	TUPLE	2	0_t OUTPUT	INT	6	1_t0i OUTPUT	BOOL	false	2_t1b [x__body calls in QIR: 0]
### M6 Prog(true)
simulator: (One, 1, True)
Adaptive_RI: OUTPUT	TUPLE	3	0_t OUTPUT	RESULT	1	1_t0r OUTPUT	INT	1	2_t1i OUTPUT	BOOL	false	3_t2b [x__body calls in QIR: 1]
Adaptive_RIF: OUTPUT	TUPLE	3	0_t OUTPUT	RESULT	1	1_t0r OUTPUT	INT	1	2_t1i OUTPUT	BOOL	false	3_t2b [x__body calls in QIR: 1]
Adaptive: OUTPUT	TUPLE	3	0_t OUTPUT	RESULT	1	1_t0r OUTPUT	INT	1	2_t1i OUTPUT	BOOL	false	3_t2b [x__body calls in QIR: 1]
### math_dc Main()
simulator: True
Adaptive_RI: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive_RIF: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
### math_dc_dbl Main()
simulator: True
Adaptive_RI: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive_RIF: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
Adaptive: OUTPUT	BOOL	false	0_b [x__body calls in QIR: 1]
### P1 Main()
simulator: 1
Adaptive_RI: PanicException: value is not addable: Var
Adaptive_RIF: PanicException: value is not addable: Var
Adaptive: OUTPUT	INT	1	0_i [x__body calls in QIR: 0]
### P1b Main()
simulator: 2
Adaptive_RI: PanicException: value is not addable: Var
Adaptive_RIF: PanicException: value is not addable: Var
Adaptive: OUTPUT	INT	2	0_i [x__body calls in QIR: 0]
### P2 Main()
simulator: 20
Adaptive_RI: PanicException: Unexpected advanced instruction at BlockId(0), instruction 4
Adaptive_RIF: PanicException: Unexpected advanced instruction at BlockId(0), instruction 4
Adaptive: OUTPUT	INT	20	0_i [x__body calls in QIR: 1]
### P3 Main()
simulator: (One, One)
Adaptive_RI: PanicException: value should be Int, got Var
Adaptive_RIF: PanicException: value should be Int, got Var
Adaptive: QSharpError: Qdk.Qsc.CapabilitiesCk.UseOfDynamicRange (https://aka.ms/qdk.qir#use-of-dynamic-range)

Stub-independent evidence: the argument of bool_record_output in the compiled QIR, before any stub:

M1 Adaptive_RI:  bool_record_output argument = ['i1 zeroext false']
M1 Adaptive_RIF: bool_record_output argument = ['i1 zeroext false']
M1 Adaptive:     bool_record_output argument = ['i1 zeroext %var_5']
M2 (all three):  bool_record_output argument = ['i1 zeroext false']
M4 (all three):  bool_record_output argument = ['i1 zeroext false']

For M1, the QIR does branch on %var_0 = call i1 @__quantum__rt__read_loss(...), and it builds a phi for y. It then records the literal false for y == 0 instead of using that phi.

The same shows in the RIR that partial evaluation produces, before any QIR or SSA pass (M2's shape, on main):

Block 0:Block:
    Call id(1), args( Pointer, )
    Call id(2), args( Qubit(0), Result(0), )
    Variable(0, Boolean) = Call id(3), args( Result(0), )
    Variable(1, Boolean) = Store Variable(0, Boolean)
    Variable(2, Integer) = Store Integer(0)
    Branch Variable(1, Boolean), 2, 1
Block 1:Block:
    Call id(4), args( Bool(false), Tag(0, 3), )
    Return Integer(0)
Block 2:Block:
    Variable(2, Integer) = Store Integer(1)
    Jump(1)

The branch on read_loss is emitted and block 2 stores 1 into y, but block 1 records the folded Bool(false). In RCA, __quantum__rt__read_loss(M(q)) and a user IsOneExt(M(q)) both get value_kind: Constant.

Root cause

  • derive_intrinsic_function_application_generator_set gives an intrinsic function's output the "constant" application Dynamic { runtime_features: empty, value_kind: Constant } when its argument is dynamic but constant (qsc_rca/src/core.rs#L2562-L2620; the constant arm is lines 2604–2607). A measured Result is such an argument. Intrinsic and @SimulatableIntrinsic callables reach this path through analyze_intrinsic_callable (core.rs#L1509).
  • Intrinsic operations do it differently: their output is Variable unless it is Unit, Qubit or Result (core.rs#L2644-L2653).
  • analyze_expr_if pushes a dynamic scope only for a Variable condition (core.rs#L771-L778). So set inside if lost { … } leaves the mutable Static.
  • Partial evaluation evaluates the condition and emits a runtime branch (qsc_partial_eval/src/lib.rs#L2530-L2538). It then evaluates the Static uses after the merge classically. This produces the literal false in the QIR, the folded n == 5, and the panics when a Static use meets a runtime Var.
  • Because the kind carries no runtime-feature flags, capability checks are bypassed as well:
    • math_dc_dbl compiles a runtime Double comparison on Adaptive_RI (and gives the wrong answer), where the same comparison on a Variable value is rejected with UseOfDynamicDouble;
    • P2 indexes an array with a runtime Int on Adaptive_RI/RIF, where UseOfDynamicIndex is expected; it panics in RIR instead.
  • The RCA side is confirmed by tests, and the folding of the Static uses by the RIR above; the exact code path inside qsc_partial_eval is from reading.

History (from earlier runs on these commits):

Version M1, M2 M3, M4, M6 M5
95c039f (before #3885) wrong ok ok
45d8e3038 (#3885) wrong wrong ok
96388a0da (#3887) and later wrong wrong wrong

At 95c039f the path not taken was already wrong; #3885 moved the error to the taken path. The family therefore predates #3885 and #3887, and reverting either would not fix it. Loss support (read_loss, MResetZChecked, IsLossResult) arrived in #2567.

Possible fixes

  1. Proposal: in derive_intrinsic_function_application_generator_set, give the constant parameter application the output kind Variable (by type, with ValueKind::new_variable_from_type) unless the output type is Unit, Qubit or Result. This is the rule intrinsic operations already use. A function of a measured Result is a runtime value, even though the Result itself is a static ID.
  2. Push a dynamic scope in analyze_expr_if for any non-Static condition, not only Variable ones. This fixes the if shapes only. The capability checks of math_dc_dbl, P2 and P3 would still be bypassed, because the value itself would still be Constant.
  3. Make partial evaluation reject (Error::Unexpected) a runtime fork on a condition that RCA did not classify as Variable, as it already does for while. This turns wrong answers into compile errors but does not make the programs compile.

Why option 1 seems right.

  • It makes RCA agree with what partial evaluation does. The Result argument reaches partial evaluation as a Result ID, which is_static_value treats as not static (qsc_partial_eval/src/lib.rs#L5021-L5032). So the call is emitted to the target and returns a fresh runtime variable (eval_expr_call_to_intrinsic, same file, L1892-L2041). Option 1 gives that variable the RCA kind Variable.
  • It changes behaviour only where an intrinsic function gets a Result argument. From reading: derive_arg_compute_kinds (core.rs#L1665-L1691) turns every dynamic-constant argument that is not a Result (or array) into Static before the application is computed, so the changed constant arm is reached only for Result-typed arguments (and for captured lambda arguments, which skip that step). Array parameters use a different application, which is unchanged. In particular, the math intrinsics (Cos, Sqrt, IntAsDouble, …) never take a Result, so their behaviour does not change. This was checked with a probe that passes an operation's Double result to Cos: the output is the same on main and with the fix.

Prototype of option 1 (on main a90b8f777; qsc_rca and qsc_partial_eval are unchanged on main 1b8de0d17): the core.rs change plus 3 new tests, which all fail without the change:

  • qsc_rca tests::calls::check_rca_for_call_to_intrinsic_function_with_measured_result_argument (user @SimulatableIntrinsic function) and check_rca_for_call_to_read_loss_with_measured_result_argument: the call is now value_kind: Variable (was Constant).
  • qsc_partial_eval tests::intrinsics::branch_on_read_loss_of_measured_result_keeps_dynamic_value_of_mutable: block 1 now loads y, compares it with 0 and records that variable, instead of Bool(false).

Existing tests that change: 16 qsc_rca tests in tests/intrinsics.rs (check_rca_for_{arc_cos, arc_sin, arc_tan, arc_tan_2, begin_estimate_caching, cos, cosh, int_as_big_int, int_as_double, log, sin, sinh, sqrt, tan, tanh, truncate}). They print each intrinsic function's generator set. The whole change is 18 lines, each constant: … value_kind: Constant becoming value_kind: Variable (ArcTan2 and BeginEstimateCaching have 2 parameters). This is exactly the changed arm, and, as argued above, it is not reached for these functions' parameter types. No other expectation changes.

Results with option 1, with a qdk wheel built from the prototype:

Program Main (1b8de0d17) With option 1
M1, M2, M3, M4, M6, math_dc wrong (as in the table) correct on Adaptive_RI, Adaptive_RIF and Adaptive
P1, P1b panic on Adaptive_RI/RIF correct (1, 2) on all three
math_dc_dbl wrong on all three UseOfDynamicDouble on Adaptive_RI; correct on Adaptive_RIF, Adaptive
P2 panic on Adaptive_RI/RIF UseOfDynamicIndex on Adaptive_RI/RIF; 20 on Adaptive (unchanged)
P3 panic on Adaptive_RI/RIF UseOfDynamicRange on all three (Adaptive unchanged)
M5 (or with loss flag) wrong on all three still wrong: (6, false); needs #3977 as well

The rejections are the expected capability errors for a runtime Double, a runtime index and a runtime range on these profiles. With the prototype, cargo test of qsc_rca, qsc_partial_eval, qsc_passes and qsc_codegen passes, and clippy (-D warnings) and cargo fmt are clean.

Relation to #3977. With #3977 alone (head 6228ff9ee), all 12 programs behave as on main, M5 included. With #3977 plus option 1, M5 is correct on all three profiles ((6, true)) and the other rows are as with option 1 alone; cargo test of qsc_codegen, qsc_partial_eval, qsc_passes and qsc_rca (254, 426, 475, 317 passed) and qsc --lib (494 passed, 1 ignored) pass, and clippy and fmt are clean. So this bug is independent of #3976, and M5 needs both fixes.

Not covered by option 1: an Int returned by a user operation (operation GetK(q : Qubit) : Int { X(q); X(q); 1 }) passed to a user intrinsic function. It is wrong (false, expected true) on Adaptive_RI/RIF both on main and with option 1. RCA gives PosExt(GetK(q)) the kind value_kind: Constant, because derive_arg_compute_kinds turns the non-Result argument into Static. But partial evaluation still emits a runtime call to PosExt and branches on its result. From reading, the cause is that partial evaluation folds intrinsic functions only for a fixed list of names (the math functions, IntAsDouble, …) and emits a call for every other intrinsic function, whatever its arguments. With a literal argument (PosExt(1)) the program is rejected with PartialEval.EvaluationFailed on main and with option 1, so only the "dynamic but constant" argument is miscompiled. A complete fix would also make RCA classify any call to an intrinsic function that partial evaluation does not fold as Variable when the call is dynamic. We did not prototype this.

How it was found

While proving properties of a formal model of the lowering: the proof needed the invariant "a Dynamic(Constant) value never hides a runtime Bool/Int", and its counterexample turned out to be reachable in qdk through intrinsic functions. Random differential testing did not find it: our generator's only runtime Bool is MResetZ(q) == One, and it has no intrinsic calls.

Related issues

  • #3283 (closed): "Arguments that are 'Dynamic Constant' treated as 'Static' across callable boundaries". It is the same kind confusion, but at a different site: callable arguments, not intrinsic-function outputs.
  • #3610 (open): "Add PEEK_LOSS functionality to Q# / QASM", for loss-checking APIs. Programs using it will meet this bug.
  • #3750 (closed): Result.Loss in callable-object compiles. This is a different bug.
  • #3976 / #3977: M5 has the and/or shape, but needs this fix as well.
  • No existing issue found for this bug. Searches: "MResetZChecked", "read_loss", "IsLossResult", "SimulatableIntrinsic", "intrinsic function constant", "Dynamic Constant", "value is not addable", "Unexpected advanced instruction", "value should be Int, got Var".

Question for the RCA owners

Option 1 changes RCA's rule for intrinsic functions: their output on a dynamic-constant argument (a measured Result) becomes Variable, as is already the rule for intrinsic operations. This changes the printed generator sets in 16 tests in qsc_rca/src/tests/intrinsics.rs (Constant → Variable in the constant application of Cos, IntAsDouble, …); from reading, behaviour changes only for Result arguments.

Is that the rule you want? Or would you prefer option 2 (a dynamic scope for any non-Static if condition) or option 3 (partial evaluation rejects such forks)? Also, the variant with an operation's Int result passed to a user intrinsic function is not fixed by any of these; should it be tracked in this issue? I will open the PR once you confirm.

Dominant language
Rust
Stars
1k
Forks
219
Avg merge
3d 19h
Merged PRs (30d)
93

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from microsoft/qdk

All issues in microsoft/qdk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.