Repository navigation
[mypyc] Fix native int shifts by negative or too large counts - #22419
Open
Jahongir-Qurbonov wants to merge 3 commits into
Open
Jahongir-Qurbonov wants to merge 3 commits into
Jahongir-Qurbonov wants to merge 3 commits into
Conversation
Shifts of native ints (i64, i32, i16, u32, u16, u8) were compiled to a plain C shift, which is undefined behavior if the count is negative or at least the bit width. In practice the count was masked, so e.g. i64 `5 >> 64` gave 5 and `8 >> -1` gave 0, and a constant count such as `x >> 64` failed the C build with -Werror. Use the plain shift only if the count is in range. Otherwise a negative count raises ValueError, a left shift gives 0 and a right shift gives 0, or -1 for a negative signed value. Constant counts are resolved at compile time. The common case needs one extra unsigned comparison. Also cast an integer literal left operand to the native int type in C, since the C shift type follows the promoted left operand (`3 << n` for i64 was a 32-bit shift), and add the missing uint16_t cast for unsigned comparisons of i16 values.
Collaborator
|
I have an alternative implementation of this in a local branch, which uses a a bit different approach. However, it depends on some other changes and won't be ready for a PR yet. I'm currently leaning towards my implementation, since it avoids raising an exception (but it doesn't match CPython semantics). I still need to think about this. |
Contributor
Author
|
Thanks, makes sense. Feel free to close this PR if you go with your implementation. If it helps, the tests in this PR may still be useful for checking the edge cases (negative and too large shift counts for each native int type). |
The u64 and i8 types were added after the shift fix was written. With master merged in, their shifts go through the same range-checked lowering, but for i8 the unsigned range check was emitted without a uint8_t cast. A negative i8 count was then promoted to a negative int, passed the check and was used as a C shift count (undefined behavior) instead of raising ValueError. Add the missing uint8_t cast for unsigned comparisons of i8 values, and add shift run tests for u64 and i8 plus irbuild tests, mirroring the existing ones for the other native int types.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Shifts of native ints (
i64,i32,i16,u32,u16,u8) were compiled to a plain C shift. In C that is undefined behavior when the count is negative or at least the bit width; on x86 the count is effectively masked:With a constant count (e.g.
x >> 64fori64, orx << 40fori32), the generated C failed to compile ("shift count >= width of type" with-Werror).Now the plain shift is only used when the count is in range:
ValueError.int(truncated or wrapped, like other native int shifts).This also fixes two related codegen issues:
3 << nwithn: i64was a 32-bit shift.i16values were missing theuint16_tcast.Added run tests for all native int types. They cover counts 0, width-1, width, width+1, 64, 100 and negative counts, both constant and variable.
Since this PR was opened,
u64(#22384) andi8(#22447) were added to master, and they had the same problem (e.g.x >> nwithx: u64 = 2**64 - 1, n: u64 = 64gave2**64 - 1instead of 0, andx << nwithx = 1, n = 64gave 1). After merging master, both types go through the same shift lowering. Fori8, the unsigned range check needed auint8_tcast, just like theuint16_tcast fori16. Without it, a negativei8count passed the check and the C code shifted by a negative count instead of raisingValueError. I added the same run tests torun-u64.testandrun-i8.test, plus irbuild tests for a constantu64count and a variablei8count.