Skip to content

Decide what a shift count at or beyond the operand width means for classical expressions #879

Description

@ciaranra

The classical expression evaluators do two different things when a shift count reaches or exceeds the operand's width, and the inconsistency is about to become visible.

In the PHIR interpreter, an unsigned shift in either direction returns zero once the count reaches the width, because it goes through the fixed-width bit shift. A signed right shift instead masks the count to 64 bits, because it goes through Rust's wrapping_shr. So a signed value shifted right by 64 comes back unchanged rather than filled with its sign bit. The Python interpreter computes the same thing, by an explicit modulo, so the behaviour is pinned bit-for-bit by the parity test and cannot be changed on the Rust side alone.

The QASM engine does neither: it clamps the count to the operand's width, so a shift by 64 or more yields zero in both directions.

#869 moves both onto one shared evaluator, so one of these has to give. The options, as far as I can see them:

  • Saturate everywhere. A count at or beyond the width yields zero for a logical shift and all sign bits for an arithmetic right shift. This is the rule most people expect, and it makes the two directions consistent. It changes PHIR behaviour for signed right shifts by 64 or more, so the Python interpreter changes with it and the parity fixtures move.
  • Keep the count masked to the width everywhere. Consistent in a different way, and matches what C compilers actually do on most hardware, but it is the behaviour people are surprised by, and it would change QASM.
  • Keep each evaluator as it is, which means the shared evaluator carries a flag, and the flag exists forever.

My recommendation is to saturate, and to change the Python interpreter in the same change so the two stay bit-for-bit. Shifting a value further than its own width is not a meaningful request, and of the two possible answers, zero and sign-fill is the one that does not depend on knowing the evaluation width.

This does not block #869. Slice B preserves the current behaviour exactly rather than inventing a rule, so the decision can be made on its own schedule, and slice C classifies whatever it inherits.

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

    questionFurther information is requestedrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions