Skip to content

QASM classical expressions guess signedness from bit patterns: division, negation width, shifts, as_i64 and folding all disagree #869

Description

@ciaranra

Summary

PECOS's QASM classical expressions have no representation of signedness: values are bare BitVecs and every operation guesses sign from the bit pattern (resize_to_same_width sign-extends any multi-bit value from its MSB, bitvec::divide treats both MSBs as signs, as_i64 reinterprets any pattern as two's complement, is_negative_expression looks only at the outermost AST node). #864 is the comparison symptom and its PR fixes comparisons and assignment widening under the rule "registers are unsigned; only an explicitly negated expression is negative", using expression shape for signedness. Everything below is the rest of the same confusion and needs one deliberate design (value-carried signedness) rather than more shape heuristics.

Reproduced on dev at 86cd2b1 (through sim_builder, 4-bit registers unless stated)

Found by review, not yet reproduced (read the cited code before acting)

  • Unary minus keeps the operand's width: negating a 2-bit unsigned 11 yields 01 (+1), not -3 (bitvec_expression.rs evaluate_unary_op, arithmetic.rs::negate). The negation width, and the policy for negating the signed minimum, are undefined.
  • as_i64 on an unsigned value with its top bit set returns a negative number. Shift counts use it, so shifting by a register holding 3 (2-bit 11) clamps the count to 0. WASM arguments use it too. Right shift is logical; whether a signed right shift exists is undefined.
  • analyze_comparison treats "outermost node is unary minus" as "negative", so -0 < 0 is true, --1 > 0 is false, and -1 < (-2 + 0) is true. The shortcut runs before operands are evaluated, in both runtime evaluation and constant folding.
  • Constant folding complements a literal at its parsed width while runtime zero-extends to the default width first, so ~1 folds to 14 but evaluates to 254 at default width 8.
  • Mixed signed/unsigned bitwise operations: 15 & -1 at width 4 is a signed 1111 (-1) and 15 ^ -1 a signed 0 under any propagation rule; whatever the rule, it needs tests.
  • Indexed assignment c[0] = 2 stores 1 (truthiness), not the low bit 0.
  • Signedness and width of RNG and WASM function results, and of the void-result placeholder, are unstated; a function result compared directly is not supported by the recursive evaluator.
  • qasm_to_phir.rs types every creg as i64 while qasm_to_phir_json.rs types it u32/u64 (QASM-to-PHIR lowerings disagree on the classical register type: i64 in the Rust path, u32/u64 in the JSON path #868).

Proposed shape

Carry signedness on the evaluated value (ExpressionValue::BitVec { bits, signed } or a signed variant), produced only by unary minus, propagated through operators, and consumed by widening, division, shifts and the i64 conversion. Registers are unsigned. Define negation width, division rounding, shift semantics and the conversion overflow policy in one place and test each with a bit-exact table. Delete the MSB-based heuristics (resize_to_same_width's sign guess, is_negative_expression's AST-shape guess) once the value carries the answer.

The review that produced this list is on the #864 PR.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrustPull requests that update rust codeseverity:highBroken functionality or loud failure in a supported path

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions