Skip to content

More comprehensive update for variable updates in static exprs - #3885

Open
Stefan J. Wernli (swernli) wants to merge 6 commits into
swernli/issue3800from
swernli/issue3801
Open

Stefan J. Wernli (swernli) wants to merge 6 commits into
swernli/issue3800from
swernli/issue3801

Conversation

@swernli

@swernli Stefan J. Wernli (swernli) commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

This adds code that better propagates variable values (including known literal values) back and forth across the hybrid and classical maps when partial eval invokes the regular evaluator for static sub-expressions.

Fixes #3801


Stack created with GitHub Stacks CLI • Give Feedback 💬

Comment thread source/compiler/qsc_eval/src/lib.rs Outdated
Comment thread source/compiler/qsc_partial_eval/src/tests/loops.rs
Comment thread source/compiler/qsc_partial_eval/src/lib.rs
@swernli
Stefan J. Wernli (swernli) force-pushed the swernli/issue3801 branch 2 times, most recently from ce69690 to 1a8b836 Compare October 7, 2026 22:53
@swernli
Stefan J. Wernli (swernli) force-pushed the swernli/issue3801 branch 2 times, most recently from b429f78 to f02d33a Compare October 8, 2026 02:56
@amcasey

Andrew Casey (amcasey) commented Oct 8, 2026 •

Copy link
Copy Markdown
Member
@EntryPoint(Adaptive)
operation Main() : Int {
    use (q1, q2) = (Qubit(), Qubit());

    mutable a = MResetZ(q1) == One ? 1 | 2;
    mutable b = MResetZ(q2) == One ? 3 | 4;
    mutable temporary = 0;

    if true {
        set temporary = a;
        set a = b;
        set b = temporary;
    }

    b
}

This should return 2, but I'm getting 0. Copilot thinks it's because we're updating the map in hash order, rather than assignment order. (Since it's hash-based, it seems possible that you won't see it on your mac.)

Comment thread source/compiler/qsc_partial_eval/src/evaluation_context.rs
@amcasey

Andrew Casey (amcasey) commented Oct 8, 2026 •

Copy link
Copy Markdown
Member
operation Main() : (Result, Result) {
    use (a, b) = (Qubit(), Qubit());
    X(b);

    mutable x = a;
    let snapshot = x;
    set x = b;

    let before = M(snapshot);
    let after = M(x);
    Reset(snapshot);
    Reset(x);
    (before, after)
}

Expected: (0, 1) / Actual: (1, 1)

It's measuring b twice:

  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))

Copilot's guess:

The new pre-sync in lib.rs:1266 copies hybrid values into the classical environment.

For mutable x:

env[x]         = Qubit(a)
hybrid_vars[x] = Var(rir_x)
static_vars[x] = Literal::Qubit(0)

The synchronization helper in evaluation_context.rs:239 tries to convert the static RIR literal back into an evaluator value. But map_rir_literal_to_eval_value in lib.rs:5403 does not convert Literal::Qubit; it returns None.

It therefore falls back to:

env[x] = Var(rir_x)

@swernli

Copy link
Copy Markdown
Contributor Author
@EntryPoint(Adaptive)
operation Main() : Int {
    use (q1, q2) = (Qubit(), Qubit());

    mutable a = MResetZ(q1) == One ? 1 | 2;
    mutable b = MResetZ(q2) == One ? 3 | 4;
    mutable temporary = 0;

    if true {
        set temporary = a;
        set a = b;
        set b = temporary;
    }

    b
}

This should return 2, but I'm getting 0. Copilot thinks it's because we're updating the map in hash order, rather than assignment order. (Since it's hash-based, it seems possible that you won't see it on your mac.)

Can you confirm if you are getting 0 for all the compatible profiles or just for Adaptive Profile? On my system, only Adaptive is returning 0, and that issue is fixed by the (current) top of the stack, #3941. The ordering could still be problem, but I tried artificially rotating the returned vector of updates and it had no effect on the behavior.

@swernli

Copy link
Copy Markdown
Contributor Author
operation Main() : (Result, Result) {
    use (a, b) = (Qubit(), Qubit());
    X(b);

    mutable x = a;
    let snapshot = x;
    set x = b;

    let before = M(snapshot);
    let after = M(x);
    Reset(snapshot);
    Reset(x);
    (before, after)
}

Expected: (0, 1) / Actual: (1, 1)

It's measuring b twice:

  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))

Copilot's guess:

The new pre-sync in lib.rs:1266 copies hybrid values into the classical environment.

For mutable x:

env[x]         = Qubit(a)
hybrid_vars[x] = Var(rir_x)
static_vars[x] = Literal::Qubit(0)

The synchronization helper in evaluation_context.rs:239 tries to convert the static RIR literal back into an evaluator value. But map_rir_literal_to_eval_value in lib.rs:5403 does not convert Literal::Qubit; it returns None.

It therefore falls back to:

env[x] = Var(rir_x)

This is fixed by fifth entry in the stack, #3901

This branch has not been deployed

No deployments
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.

[Miscompile] An assignment inside a compile-time if/for/while is lost when a later branch isn't taken

2 participants