Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: shift operators #3019

Merged
merged 19 commits into from
Apr 24, 2023
Merged

Conversation

charles-cooper
Copy link
Member

@charles-cooper charles-cooper commented Aug 1, 2022

What I did

enable x << y and x >> y

How I did it

added codegen rules

How to verify it

TBD

Commit message

Commit message for the final, squashed PR. (Optional, but reviewers will appreciate it! Please see our commit message style guide for what we would ideally like to see in a commit message.)

Description for the changelog

Cute Animal Picture

image

@codecov-commenter
Copy link

codecov-commenter commented Aug 1, 2022

Codecov Report

Merging #3019 (7b575a6) into master (c93bc06) will decrease coverage by 0.23%.
The diff coverage is 80.95%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@            Coverage Diff             @@
##           master    #3019      +/-   ##
==========================================
- Coverage   88.96%   88.73%   -0.23%     
==========================================
  Files          84       84              
  Lines       10600    10636      +36     
  Branches     2211     2221      +10     
==========================================
+ Hits         9430     9438       +8     
- Misses        766      793      +27     
- Partials      404      405       +1     
Impacted Files Coverage Δ
vyper/builtins/functions.py 88.92% <40.00%> (-1.92%) ⬇️
vyper/codegen/expr.py 87.56% <69.23%> (-0.63%) ⬇️
vyper/semantics/types/primitives.py 92.71% <87.50%> (+0.18%) ⬆️
vyper/ast/nodes.py 94.21% <100.00%> (+0.11%) ⬆️
vyper/semantics/analysis/utils.py 92.05% <100.00%> (+0.07%) ⬆️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@charles-cooper
Copy link
Member Author

note: as this PR currently stands it uses the existing rules for types_from_BinOp, which enforces that the two operands to the binop operator have the same type. (the same logic exists in validating AugAssign nodes).

this means that in the expression x << y or x >> y, y can be a negative number. we can (and should) add special rules to enforce that y is an unsigned integer but not for the 0.3.5 release since the fix is a little more involved.

@charles-cooper charles-cooper marked this pull request as draft August 1, 2022 15:28
@charles-cooper
Copy link
Member Author

note to self: the fuzzer tests for this are getting sigkilled for some reason on my local machine

@charles-cooper charles-cooper marked this pull request as ready for review April 19, 2023 21:34
@charles-cooper
Copy link
Member Author

i fixed the type checking by adding a carveout for the new shift operators during type checking.

@charles-cooper
Copy link
Member Author

note that this changes the compile-time folding for shift to be slightly stricter than the existing shift() builtin. shift() would wrap a left-shifted value that was out of bounds of uint256, but the << operator will raise an OverflowException.

@charles-cooper
Copy link
Member Author

@fubuloubu quick check on the docs updates and then I will merge

@charles-cooper charles-cooper merged commit 7a64d4b into vyperlang:master Apr 24, 2023
@charles-cooper charles-cooper deleted the feat/ast_shift branch April 24, 2023 22:24
``x >> y`` Right shift
============= ======================

Shifting is only available for 256-bit wide types. That is, ``x`` must be ``int256``, and ``y`` can be any unsigned integer. The right shift for ``int256`` compiles to a signed right shift (EVM ``SAR`` instruction).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be confused since I haven't checked yet the implementation details, but x can be uint256 OR int256 (here you say x must be int256 and below you say x must be uint256). What you probably mean (that's how I understand it) is that for logical shifting x must be uint256 whilst for arithmetic shift operations x must be int256.

Furthermore, it might be useful to add to the docs that the right shift x >> y for negative x is rounded down (towards negative infinity). The reason why I mention this is that in Solidity versions <0.5.0 the same expression (which is equivalent to the mathematical expression x / 2**y) was rounded towards zero. So we're sure people don't take wrong assumptions here.

Eventually, I also think it's important to highlight that the runtime overflow always returns 0 and not anything else. This contradicts the intuition we have from unchecked arithmetic where everything wraps and starts all over again from 0 but can have any uint256 result. What I mean by that is:

@external
@pure
def foo(x: uint8, y: uint8) -> uint8:
    return unsafe_add(x, y)

ExampleContract.foo(255, 255) will result in 254 and not 0.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be confused since I haven't checked yet the implementation details, but x can be uint256 OR int256 (here you say x must be int256 and below you say x must be uint256). What you probably mean (that's how I understand it) is that for logical shifting x must be uint256 whilst for arithmetic shift operations x must be int256.

Not sure if I wrote it as clearly as possible, but the rationale was that the docs were already split by signed and unsigned integers, and so I split the explanations by section. The first section was already discussing signed integers, and the second section was already discussing unsigned integers.

Furthermore, it might be useful to add to the docs that the right shift x >> y for negative x is rounded down (towards negative infinity). The reason why I mention this is that in Solidity versions <0.5.0 the same expression (which is equivalent to the mathematical expression x / 2**y) was rounded towards zero. So we're sure people don't take wrong assumptions here.

Eventually, I also think it's important to highlight that the runtime overflow always returns 0 and not anything else. This contradicts the intuition we have from unchecked arithmetic where everything wraps and starts all over again from 0 but can have any uint256 result.

I don't really think this is necessary - I made sure to explain that the shift operator compiles directly to the SHR/SAR instruction, whose behavior is well known (and is also clarified at length in the text of EIP-145).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I wrote it as clearly as possible, but the rationale was that the docs were already split by signed and unsigned integers, and so I split the explanations by section. The first section was already discussing signed integers, and the second section was already discussing unsigned integers.

Right, that makes actually sense :)

I don't really think this is necessary - I made sure to explain that the shift operator compiles directly to the SHR/SAR instruction, whose behavior is well known (and is also clarified at length in the text of EIP-145).

Ok fair enough, but I doubt many folks even know about this EIP lol.

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.

4 participants