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: O(1) selector tables #3496

Merged
merged 96 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
ef474ba
wip jumptables
charles-cooper Feb 8, 2022
20b5b1d
small refactor, add bucket datastructure
charles-cooper May 26, 2023
391f3b5
use smaller buckets
charles-cooper May 28, 2023
eb3d54f
don't need primes
charles-cooper May 28, 2023
9f53fff
clean up primes generation
charles-cooper Jul 4, 2023
ac651e7
small cleanup of jumptables
charles-cooper Jul 4, 2023
9ca04d4
refactor selector table generation
charles-cooper Jul 4, 2023
03defa6
lint jumptable
charles-cooper Jul 4, 2023
471643b
export jumptable info better
charles-cooper Jul 4, 2023
6428ce0
feat: add optimization mode to vyper compiler (#3493)
charles-cooper Jul 11, 2023
8bb5734
add bench for jumptable
charles-cooper Jul 5, 2023
8113cb7
jumptable: add fall back to exhaustive search if no magic found
charles-cooper Jul 5, 2023
49c2491
put in min calldatasize info
charles-cooper Jul 5, 2023
c0b200e
add bench for imperfect jumptable
charles-cooper Jul 7, 2023
3341193
wip sparse jumptable
charles-cooper Jul 7, 2023
1c8bd81
switch based on optimization mode
charles-cooper Jul 11, 2023
aae4679
add notes on amortization
charles-cooper Jul 11, 2023
422273b
wip sparse jumptable
charles-cooper Jul 12, 2023
dead92d
add jumptable data for sparse mode
charles-cooper Jul 12, 2023
c9c5181
add a note
charles-cooper Jul 13, 2023
5322831
sparse jumptable: remove global calldatasize check
charles-cooper Jul 13, 2023
7d78e6b
get pipeline through bytecode working
charles-cooper Jul 13, 2023
a869935
fix type of bytecode output
charles-cooper Jul 13, 2023
aa2a04e
add a note
charles-cooper Jul 13, 2023
e4377ec
elide a goto
charles-cooper Jul 13, 2023
f99cd2d
improve runtime header a bit
charles-cooper Jul 13, 2023
134107c
get dense jumptable working (at least compiles)
charles-cooper Jul 13, 2023
92ad539
wip - fix some encodings
charles-cooper Jul 13, 2023
4be2f3d
relocate data sections to end
charles-cooper Jul 13, 2023
d0cc42b
Merge branch 'master' into jumptables
charles-cooper Jul 13, 2023
34848f3
fix lint, mypy
charles-cooper Jul 13, 2023
10a1e49
handle 0 external methods
charles-cooper Jul 13, 2023
6309c14
fix opcode disassembler
charles-cooper Jul 13, 2023
2f0602b
git rid of debug asserts
charles-cooper Jul 13, 2023
1e4dfa7
fix bucket size
charles-cooper Jul 13, 2023
e1bf01b
allow trailing pushes
charles-cooper Jul 13, 2023
57caf9d
fix function metadata ordering
charles-cooper Jul 13, 2023
c7e7a69
clarify data section relocation, fix busted postambles
charles-cooper Jul 13, 2023
011dd6e
move runtime code to end
charles-cooper Jul 13, 2023
22085dc
fix lint
charles-cooper Jul 13, 2023
674157f
handle no external functions in sparse case
charles-cooper Jul 13, 2023
a44271b
fix lint
charles-cooper Jul 14, 2023
7f5617b
Merge branch 'master' into jumptables
charles-cooper Jul 15, 2023
2779afc
add codesize switch
charles-cooper Jul 15, 2023
37456c3
fix lint
charles-cooper Jul 15, 2023
4c0aa48
remove skip_nonpayable_check kwarg
charles-cooper Jul 15, 2023
6a6ee7f
rename jumptable module
charles-cooper Jul 15, 2023
a2fbe2b
fix opt none selector table
charles-cooper Jul 16, 2023
1f20075
rename helper function
charles-cooper Jul 16, 2023
fad71ab
remove some dead code
charles-cooper Jul 16, 2023
1df2db3
add some docs about optimization modes
charles-cooper Jul 16, 2023
0b68fca
export the symbol map
charles-cooper Jul 17, 2023
6dc9077
add debug mode
charles-cooper Jul 17, 2023
3005c58
add selector table tests
charles-cooper Jul 17, 2023
8a1337f
fix some small bugs
charles-cooper Jul 17, 2023
8a96902
fix a signature
charles-cooper Jul 17, 2023
c4794df
fix some test bugs
charles-cooper Jul 17, 2023
ed070ac
add override opt level
charles-cooper Jul 17, 2023
ae095e1
fix max_examples
charles-cooper Jul 17, 2023
0e6847f
add linear jumptable for opt-none mode
charles-cooper Jul 18, 2023
f72d1c0
fix max calldata bytes
charles-cooper Jul 18, 2023
41077a6
use an iterator
charles-cooper Jul 18, 2023
155a126
add back deleted code
charles-cooper Jul 18, 2023
752a60e
fix mypy
charles-cooper Jul 18, 2023
24eda19
bump max_examples for selector table
charles-cooper Jul 18, 2023
d465136
move slice fuzz tests back to mark.fuzzing
charles-cooper Jul 18, 2023
52f386e
Merge branch 'master' into jumptables
charles-cooper Jul 18, 2023
3176fc8
remove dead comments
charles-cooper Jul 18, 2023
bf13ccb
fix some api changes to tests
charles-cooper Jul 18, 2023
581c168
fix lint
charles-cooper Jul 18, 2023
1683f96
fix matrix debug name
charles-cooper Jul 19, 2023
1c64e4c
enable debug mode in tests
charles-cooper Jul 19, 2023
5a9ec1e
fix name
charles-cooper Jul 19, 2023
ac96fe1
remove some double quotes
charles-cooper Jul 19, 2023
8825938
change falsey value
charles-cooper Jul 19, 2023
5317f1f
fix lint
charles-cooper Jul 19, 2023
97c4b7d
fix test config
charles-cooper Jul 19, 2023
9e6fe53
randomize n bytes stripped
charles-cooper Jul 19, 2023
363f884
add tests for default args
charles-cooper Jul 19, 2023
9359f09
fix lint
charles-cooper Jul 19, 2023
1f7bd30
add closing goto fallback
charles-cooper Jul 20, 2023
12e4b39
fix a comment
charles-cooper Jul 21, 2023
8b98bcd
improve cli help
charles-cooper Jul 21, 2023
de7a450
fix lint
charles-cooper Jul 21, 2023
ffeb8d6
test default settings
charles-cooper Jul 21, 2023
283f168
add back in __init__.py file
charles-cooper Jul 21, 2023
35b2e80
add different default function configurations to fuzzer
charles-cooper Jul 21, 2023
1c9f96c
fix an f-string
charles-cooper Jul 21, 2023
16b91ba
fix: logging from non-mutating default function
charles-cooper Jul 21, 2023
af0f60b
add some jumptable stats tests
charles-cooper Jul 21, 2023
d3d8b4d
update a comment
charles-cooper Jul 23, 2023
9cfb797
revert API change to assembly_to_evm
charles-cooper Jul 24, 2023
8095ee8
clean up a comment
charles-cooper Jul 25, 2023
1a46120
update some comments
charles-cooper Jul 25, 2023
c7c8f73
Merge branch 'master' into jumptables
charles-cooper Jul 25, 2023
ab1638f
simplify linear selector table
charles-cooper Jul 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,18 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [["3.10", "310"], ["3.11", "311"]]
python-version: [["3.11", "311"]]
# run in modes: --optimize [gas, none, codesize]
flag: ["core", "no-opt", "codesize"]
opt-mode: ["gas", "none", "codesize"]
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
debug: [true, false]
# run across other python versions.# we don't really need to run all
# modes across all python versions - one is enough
include:
- python-version: ["3.10", "310"]
opt-mode: gas
debug: false

name: py${{ matrix.python-version[1] }}-${{ matrix.flag }}
name: py${{ matrix.python-version[1] }}-opt-${{ matrix.opt-mode }}${{ matrix.debug && '-debug' || '' }}

steps:
- uses: actions/checkout@v1
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -97,7 +104,7 @@ jobs:
run: pip install tox

- name: Run Tox
run: TOXENV=py${{ matrix.python-version[1] }}-${{ matrix.flag }} tox -r -- --reruns 10 --reruns-delay 1 -r aR tests/
run: TOXENV=py${{ matrix.python-version[1] }} tox -r -- --optimize ${{ matrix.opt-mode }} ${{ matrix.debug && '--enable-compiler-debug-mode' || '' }} --reruns 10 --reruns-delay 1 -r aR tests/

- name: Upload Coverage
uses: codecov/codecov-action@v1
Expand Down
17 changes: 17 additions & 0 deletions docs/compiling-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,23 @@ Remix IDE

While the Vyper version of the Remix IDE compiler is updated on a regular basis, it might be a bit behind the latest version found in the master branch of the repository. Make sure the byte code matches the output from your local compiler.

.. _optimization-mode:

Compiler Optimization Modes
===========================

The vyper CLI tool accepts an optimization mode ``"none"``, ``"codesize"``, or ``"gas"`` (default). It can be set using the ``--optimize`` flag. For example, invoking ``vyper --optimize codesize MyContract.vy`` will compile the contract, optimizing for code size. As a rough summary of the differences between gas and codesize mode, in gas optimized mode, the compiler will try to generate bytecode which minimizes gas (up to a point), including:
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved

* using a sparse selector table which optimizes for gas over codesize
* inlining some constants, and
* trying to unroll some loops, especially for data copies.

In codesize optimized mode, the compiler will try hard to minimize codesize by

* using a dense selector table
* out-lining code, and
* using more loops for data copies.


.. _evm-version:

Expand Down
4 changes: 2 additions & 2 deletions docs/structure-of-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ In the above examples, the contract will only compile with Vyper versions ``0.3.
Optimization Mode
-----------------

The optimization mode can be one of ``"none"``, ``"codesize"``, or ``"gas"`` (default). For instance, the following contract will be compiled in a way which tries to minimize codesize:
The optimization mode can be one of ``"none"``, ``"codesize"``, or ``"gas"`` (default). For example, adding the following line to a contract will cause it to try to optimize for codesize:

.. code-block:: python

#pragma optimize codesize

The optimization mode can also be set as a compiler option. If the compiler option conflicts with the source code pragma, an exception will be raised and compilation will not continue.
The optimization mode can also be set as a compiler option, which is documented in :ref:`optimization-mode`. If the compiler option conflicts with the source code pragma, an exception will be raised and compilation will not continue.

EVM Version
-----------------
Expand Down
4 changes: 2 additions & 2 deletions tests/base_conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ def w3(tester):
return w3


def _get_contract(w3, source_code, optimize, *args, **kwargs):
def _get_contract(w3, source_code, optimize, *args, override_opt_level=None, **kwargs):
settings = Settings()
settings.evm_version = kwargs.pop("evm_version", None)
settings.optimize = optimize
settings.optimize = override_opt_level or optimize
out = compiler.compile_code(
source_code,
# test that metadata gets generated
Expand Down
4 changes: 2 additions & 2 deletions tests/cli/vyper_json/test_parse_args_vyperjson.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_to_stdout(tmp_path, capfd):
_parse_args([path.absolute().as_posix()])
out, _ = capfd.readouterr()
output_json = json.loads(out)
assert _no_errors(output_json)
assert _no_errors(output_json), (INPUT_JSON, output_json)
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
assert "contracts/foo.vy" in output_json["sources"]
assert "contracts/bar.vy" in output_json["sources"]

Expand All @@ -71,7 +71,7 @@ def test_to_file(tmp_path):
assert output_path.exists()
with output_path.open() as fp:
output_json = json.load(fp)
assert _no_errors(output_json)
assert _no_errors(output_json), (INPUT_JSON, output_json)
assert "contracts/foo.vy" in output_json["sources"]
assert "contracts/bar.vy" in output_json["sources"]

Expand Down
2 changes: 2 additions & 0 deletions tests/compiler/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# prevent module name collision between tests/compiler/test_pre_parser.py
# and tests/ast/test_pre_parser.py
27 changes: 27 additions & 0 deletions tests/compiler/test_default_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from vyper.codegen import core
from vyper.compiler.phases import CompilerData
from vyper.compiler.settings import OptimizationLevel, _is_debug_mode


def test_default_settings():
source_code = ""
compiler_data = CompilerData(source_code)
_ = compiler_data.vyper_module # force settings to be computed

assert compiler_data.settings.optimize == OptimizationLevel.GAS


def test_default_opt_level():
assert OptimizationLevel.default() == OptimizationLevel.GAS


def test_codegen_opt_level():
assert core._opt_level == OptimizationLevel.GAS
assert core._opt_gas() is True
assert core._opt_none() is False
assert core._opt_codesize() is False


def test_debug_mode(pytestconfig):
debug_mode = pytestconfig.getoption("enable_compiler_debug_mode")
assert _is_debug_mode() == debug_mode
10 changes: 9 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from vyper import compiler
from vyper.codegen.ir_node import IRnode
from vyper.compiler.settings import OptimizationLevel
from vyper.compiler.settings import OptimizationLevel, _set_debug_mode
from vyper.ir import compile_ir, optimizer

from .base_conftest import VyperContract, _get_contract, zero_gas_price_strategy
Expand Down Expand Up @@ -43,6 +43,7 @@ def pytest_addoption(parser):
default="gas",
help="change optimization mode",
)
parser.addoption("--enable-compiler-debug-mode", action="store_true")


@pytest.fixture(scope="module")
Expand All @@ -51,6 +52,13 @@ def optimize(pytestconfig):
return OptimizationLevel.from_string(flag)


@pytest.fixture(scope="session", autouse=True)
def debug(pytestconfig):
debug = pytestconfig.getoption("enable_compiler_debug_mode")
assert isinstance(debug, bool)
_set_debug_mode(debug)


@pytest.fixture
def keccak():
return Web3.keccak
Expand Down
15 changes: 11 additions & 4 deletions tests/parser/functions/test_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pytest
from hypothesis import given, settings

from vyper.compiler.settings import OptimizationLevel
from vyper.exceptions import ArgumentException, TypeMismatch

_fun_bytes32_bounds = [(0, 32), (3, 29), (27, 5), (0, 5), (5, 3), (30, 2)]
Expand Down Expand Up @@ -33,12 +34,15 @@ def slice_tower_test(inp1: Bytes[50]) -> Bytes[50]:

@pytest.mark.parametrize("literal_start", (True, False))
@pytest.mark.parametrize("literal_length", (True, False))
@pytest.mark.parametrize("opt_level", list(OptimizationLevel))
@given(start=_draw_1024, length=_draw_1024, length_bound=_draw_1024_1, bytesdata=_bytes_1024)
@settings(max_examples=25, deadline=None)
@settings(max_examples=100, deadline=None)
@pytest.mark.fuzzing
def test_slice_immutable(
get_contract,
assert_compile_failed,
assert_tx_failed,
opt_level,
bytesdata,
start,
literal_start,
Expand All @@ -64,7 +68,7 @@ def do_splice() -> Bytes[{length_bound}]:
"""

def _get_contract():
return get_contract(code, bytesdata, start, length)
return get_contract(code, bytesdata, start, length, override_opt_level=opt_level)

if (
(start + length > length_bound and literal_start and literal_length)
Expand All @@ -84,12 +88,15 @@ def _get_contract():
@pytest.mark.parametrize("location", ("storage", "calldata", "memory", "literal", "code"))
@pytest.mark.parametrize("literal_start", (True, False))
@pytest.mark.parametrize("literal_length", (True, False))
@pytest.mark.parametrize("opt_level", list(OptimizationLevel))
@given(start=_draw_1024, length=_draw_1024, length_bound=_draw_1024_1, bytesdata=_bytes_1024)
@settings(max_examples=25, deadline=None)
@settings(max_examples=100, deadline=None)
@pytest.mark.fuzzing
def test_slice_bytes(
get_contract,
assert_compile_failed,
assert_tx_failed,
opt_level,
location,
bytesdata,
start,
Expand Down Expand Up @@ -133,7 +140,7 @@ def do_slice(inp: Bytes[{length_bound}], start: uint256, length: uint256) -> Byt
"""

def _get_contract():
return get_contract(code, bytesdata)
return get_contract(code, bytesdata, override_opt_level=opt_level)

data_length = len(bytesdata) if location == "literal" else length_bound
if (
Expand Down
Loading
Loading