Skip to content

Commit

Permalink
feat: arithmetic for new int types (#2843)
Browse files Browse the repository at this point in the history
this commit generalizes codegen for arithmetic ops to handle any
integer type. to do so, it refactors codegen for arithmetic ops into its
own module. it also slightly changes the codegen so that it targets
optimizer rules better.

this commit also changes DecimalInfo.divisor to be of `int` type. this
is slightly less convenient, but it's clearer when used that it is not
getting truncated.

also, fix a pytest-split setting. the (default) `duration_based_chunks`
splitting algorithm in pytest-split resulted in a 0-size chunk getting
allocated to the final group, which caused tox to error out with
InvocationError code 5.
  • Loading branch information
charles-cooper authored Jun 17, 2022
1 parent a37bbbc commit daeae11
Show file tree
Hide file tree
Showing 18 changed files with 1,069 additions and 1,311 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ jobs:
# NOTE: if the tests get poorly distributed, run this and commit the resulting `.test_durations` file to the `vyper-test-durations` repo.
# `TOXENV=fuzzing tox -r -- --store-durations --reruns 10 --reruns-delay 1 -r aR tests/`
- name: Fetch test-durations
run: curl --location "https://raw.githubusercontent.com/vyperlang/vyper-test-durations/54ee7f6a09bd94192d01f8de5293483414295e45/test_durations" -o .test_durations
run: curl --location "https://raw.githubusercontent.com/vyperlang/vyper-test-durations/4d8398e581f183de986892c5a8d4ab3d05ccaab2/test_durations" -o .test_durations

- name: Run Tox
run: TOXENV=fuzzing tox -r -- --splits 45 --group ${{ matrix.group }} --reruns 10 --reruns-delay 1 -r aR tests/
run: TOXENV=fuzzing tox -r -- --splits 45 --group ${{ matrix.group }} --splitting-algorithm least_duration --reruns 10 --reruns-delay 1 -r aR tests/

- name: Upload Coverage
uses: codecov/codecov-action@v1
Expand Down
23 changes: 22 additions & 1 deletion tests/ast/nodes/test_evaluate_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from vyper.exceptions import UnfoldableNode


# TODO expand to all signed types
@pytest.mark.fuzzing
@settings(max_examples=50, deadline=1000)
@given(left=st.integers(), right=st.integers())
@pytest.mark.parametrize("op", ["==", "!=", "<", "<=", ">=", ">"])
def test_compare_eq(get_contract, op, left, right):
def test_compare_eq_signed(get_contract, op, left, right):
source = f"""
@external
def foo(a: int128, b: int128) -> bool:
Expand All @@ -25,6 +26,26 @@ def foo(a: int128, b: int128) -> bool:
assert contract.foo(left, right) == new_node.value


# TODO expand to all unsigned types
@pytest.mark.fuzzing
@settings(max_examples=50, deadline=1000)
@given(left=st.integers(min_value=0), right=st.integers(min_value=0))
@pytest.mark.parametrize("op", ["==", "!=", "<", "<=", ">=", ">"])
def test_compare_eq_unsigned(get_contract, op, left, right):
source = f"""
@external
def foo(a: uint128, b: uint128) -> bool:
return a {op} b
"""
contract = get_contract(source)

vyper_ast = vy_ast.parse_to_ast(f"{left} {op} {right}")
old_node = vyper_ast.body[0].value
new_node = old_node.evaluate()

assert contract.foo(left, right) == new_node.value


@pytest.mark.fuzzing
@settings(max_examples=20, deadline=500)
@given(left=st.integers(), right=st.lists(st.integers(), min_size=1, max_size=16))
Expand Down
11 changes: 11 additions & 0 deletions tests/compiler/ir/test_optimize_ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@
(["mod", "x", 128], ["and", "x", 127]),
(["sdiv", "x", 64], None),
(["smod", "x", 64], None),
(["exp", 3, 5], [3 ** 5]),
(["exp", 3, 256], [(3 ** 256) % (2 ** 256)]),
(["exp", 2, 257], [0]),
(["exp", "x", 0], [1]),
(["exp", "x", 1], ["x"]),
(["exp", 1, "x"], [1]),
(["exp", 0, "x"], ["iszero", "x"]),
# bitwise ops
(["shr", 0, "x"], ["x"]),
(["sar", 0, "x"], ["x"]),
Expand All @@ -102,6 +109,7 @@
(["and", "x", 1], None),
(["or", "x", 1], None),
(["xor", 0, "x"], ["x"]),
(["xor", "x", "x"], [0]),
(["iszero", ["or", "x", 1]], [0]),
(["iszero", ["or", 2, "x"]], [0]),
(["iszero", ["or", 1, ["sload", 0]]], None),
Expand All @@ -113,6 +121,9 @@
(["eq", -1, ["add", 2 ** 255, 2 ** 255 - 1]], [1]), # test compile-time wrapping
(["eq", -1, ["add", -(2 ** 255), 2 ** 255 - 1]], [1]), # test compile-time wrapping
(["eq", -2, ["add", 2 ** 256 - 1, 2 ** 256 - 1]], [1]), # test compile-time wrapping
(["eq", "x", "x"], [1]),
(["eq", "callvalue", "callvalue"], None),
(["ne", "x", "x"], [0]),
]


Expand Down
2 changes: 1 addition & 1 deletion tests/fuzzing/test_exponents.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from hypothesis import example, given, settings
from hypothesis import strategies as st

from vyper.codegen.expr import calculate_largest_base, calculate_largest_power
from vyper.codegen.arithmetic import calculate_largest_base, calculate_largest_power


@pytest.mark.fuzzing
Expand Down
63 changes: 59 additions & 4 deletions tests/parser/types/numbers/test_decimals.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from decimal import Decimal, getcontext
from decimal import ROUND_DOWN, Decimal, getcontext

import pytest

from vyper.exceptions import DecimalOverrideException, TypeMismatch
from vyper.utils import DECIMAL_EPSILON, SizeLimits


def test_decimal_override():
Expand All @@ -11,6 +12,10 @@ def test_decimal_override():
getcontext().prec = 100


def quantize(x: Decimal) -> Decimal:
return x.quantize(DECIMAL_EPSILON, rounding=ROUND_DOWN)


def test_decimal_test(get_contract_with_gas_estimation):
decimal_test = """
@external
Expand Down Expand Up @@ -131,10 +136,60 @@ def _num_mul(x: decimal, y: decimal) -> decimal:

c = get_contract_with_gas_estimation(mul_code)

NUM_1 = Decimal("85070591730234615865843651857942052864")
NUM_2 = Decimal("136112946768375385385349842973")
x = Decimal("85070591730234615865843651857942052864")
y = Decimal("136112946768375385385349842973")

assert_tx_failed(lambda: c._num_mul(x, y))

x = SizeLimits.MAX_AST_DECIMAL
y = 1 + DECIMAL_EPSILON

assert_tx_failed(lambda: c._num_mul(x, y))

assert c._num_mul(x, Decimal(1)) == x

assert c._num_mul(x, 1 - DECIMAL_EPSILON) == quantize(x * (1 - DECIMAL_EPSILON))

x = SizeLimits.MIN_AST_DECIMAL
assert c._num_mul(x, 1 - DECIMAL_EPSILON) == quantize(x * (1 - DECIMAL_EPSILON))


# division failure modes(!)
def test_div_overflow(get_contract, assert_tx_failed):
code = """
@external
def foo(x: decimal, y: decimal) -> decimal:
return x / y
"""

c = get_contract(code)

x = SizeLimits.MIN_AST_DECIMAL
y = -DECIMAL_EPSILON

assert_tx_failed(lambda: c.foo(x, y))
assert_tx_failed(lambda: c.foo(x, Decimal(0)))
assert_tx_failed(lambda: c.foo(y, Decimal(0)))

y = Decimal(1) - DECIMAL_EPSILON # 0.999999999
assert_tx_failed(lambda: c.foo(x, y))

y = Decimal(-1)
assert_tx_failed(lambda: c.foo(x, y))

assert c.foo(x, Decimal(1)) == x
assert c.foo(x, 1 + DECIMAL_EPSILON) == quantize(x / (1 + DECIMAL_EPSILON))

x = SizeLimits.MAX_AST_DECIMAL

assert_tx_failed(lambda: c.foo(x, DECIMAL_EPSILON))

y = Decimal(1) - DECIMAL_EPSILON
assert_tx_failed(lambda: c.foo(x, y))

assert c.foo(x, Decimal(1)) == x

assert_tx_failed(lambda: c._num_mul(NUM_1, NUM_2))
assert c.foo(x, 1 + DECIMAL_EPSILON) == quantize(x / (1 + DECIMAL_EPSILON))


def test_decimal_min_max_literals(assert_tx_failed, get_contract_with_gas_estimation):
Expand Down
212 changes: 0 additions & 212 deletions tests/parser/types/numbers/test_int128.py

This file was deleted.

Loading

0 comments on commit daeae11

Please sign in to comment.