Skip to content

Commit

Permalink
refactor[codegen]: make settings into a global object (#3929)
Browse files Browse the repository at this point in the history
this commit adds a global settings object, unifying various functions
which modify global settings:
anchor_opt_level, anchor_evm_version, _set_debug
  • Loading branch information
charles-cooper committed Apr 10, 2024
1 parent b43ffac commit e4f1c24
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 130 deletions.
53 changes: 34 additions & 19 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@
from vyper.ast.grammar import parse_vyper_source
from vyper.codegen.ir_node import IRnode
from vyper.compiler.input_bundle import FilesystemInputBundle, InputBundle
from vyper.compiler.settings import OptimizationLevel, Settings, _set_debug_mode
from vyper.evm.opcodes import version_check
from vyper.compiler.settings import (
OptimizationLevel,
Settings,
get_global_settings,
set_global_settings,
)
from vyper.evm.opcodes import EVM_VERSIONS, version_check
from vyper.exceptions import EvmVersionException
from vyper.ir import compile_ir, optimizer
from vyper.utils import ERC5202_PREFIX, keccak256
Expand Down Expand Up @@ -65,7 +70,7 @@ def pytest_addoption(parser):

parser.addoption(
"--evm-version",
choices=list(evm.EVM_VERSIONS.keys()),
choices=list(EVM_VERSIONS.keys()),
default="shanghai",
help="set evm version",
)
Expand All @@ -81,17 +86,17 @@ def output_formats():
return output_formats


@pytest.fixture(scope="module")
@pytest.fixture(scope="session")
def optimize(pytestconfig):
flag = pytestconfig.getoption("optimize")
return OptimizationLevel.from_string(flag)


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


@pytest.fixture(scope="session")
Expand All @@ -101,6 +106,28 @@ def experimental_codegen(pytestconfig):
return ret


@pytest.fixture(scope="session")
def evm_version(pytestconfig):
# note: we configure the evm version that we emit code for,
# but eth-tester is only configured with the latest mainnet
# version. luckily, evms are backwards compatible.
evm_version_str = pytestconfig.getoption("evm_version")
assert isinstance(evm_version_str, str)
return evm_version_str


@pytest.fixture(scope="session", autouse=True)
def global_settings(evm_version, experimental_codegen, optimize, debug):
evm.DEFAULT_EVM_VERSION = evm_version
settings = Settings(
optimize=optimize,
evm_version=evm_version,
experimental_codegen=experimental_codegen,
debug=debug,
)
set_global_settings(settings)


@pytest.fixture(autouse=True)
def check_venom_xfail(request, experimental_codegen):
if not experimental_codegen:
Expand All @@ -124,18 +151,6 @@ def _xfail(*args, **kwargs):
return _xfail


@pytest.fixture(scope="session", autouse=True)
def evm_version(pytestconfig):
# note: we configure the evm version that we emit code for,
# but eth-tester is only configured with the latest mainnet
# version.
evm_version_str = pytestconfig.getoption("evm_version")
evm.DEFAULT_EVM_VERSION = evm_version_str
# this should get overridden by anchor_evm_version,
# but set it anyway
evm.active_evm_version = evm.EVM_VERSIONS[evm_version_str]


@pytest.fixture
def chdir_tmp_path(tmp_path):
# this is useful for when you want imports to have relpaths
Expand Down Expand Up @@ -364,7 +379,7 @@ def _get_contract(
input_bundle=None,
**kwargs,
):
settings = Settings()
settings = get_global_settings()
settings.optimize = override_opt_level or optimize
settings.experimental_codegen = experimental_codegen
out = compiler.compile_code(
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/cli/vyper_json/test_get_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_unknown_evm():


@pytest.mark.parametrize(
"evm_version",
"evm_version_str",
[
"homestead",
"tangerineWhistle",
Expand All @@ -22,11 +22,11 @@ def test_unknown_evm():
"berlin",
],
)
def test_early_evm(evm_version):
def test_early_evm(evm_version_str):
with pytest.raises(JSONError):
get_evm_version({"settings": {"evmVersion": evm_version}})
get_evm_version({"settings": {"evmVersion": evm_version_str}})


@pytest.mark.parametrize("evm_version", ["london", "paris", "shanghai", "cancun"])
def test_valid_evm(evm_version):
assert evm_version == get_evm_version({"settings": {"evmVersion": evm_version}})
@pytest.mark.parametrize("evm_version_str", ["london", "paris", "shanghai", "cancun"])
def test_valid_evm(evm_version_str):
assert evm_version_str == get_evm_version({"settings": {"evmVersion": evm_version_str}})
11 changes: 5 additions & 6 deletions tests/unit/compiler/ir/test_optimize_ir.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import pytest

from vyper.codegen.ir_node import IRnode
from vyper.evm.opcodes import EVM_VERSIONS, anchor_evm_version
from vyper.evm.opcodes import version_check
from vyper.exceptions import StaticAssertionException
from vyper.ir import optimizer

POST_CANCUN = {k: v for k, v in EVM_VERSIONS.items() if v >= EVM_VERSIONS["cancun"]}


optimize_list = [
(["eq", 1, 2], [0]),
(["lt", 1, 2], [1]),
Expand Down Expand Up @@ -368,9 +365,9 @@ def test_operator_set_values():


@pytest.mark.parametrize("ir", mload_merge_list)
@pytest.mark.parametrize("evm_version", list(POST_CANCUN.keys()))
def test_mload_merge(ir, evm_version):
with anchor_evm_version(evm_version):
# TODO: use something like pytest.mark.post_cancun
if version_check(begin="cancun"):
optimized = optimizer.optimize(IRnode.from_list(ir[0]))
if ir[1] is None:
# no-op, assert optimizer does nothing
Expand All @@ -379,3 +376,5 @@ def test_mload_merge(ir, evm_version):
expected = IRnode.from_list(ir[1])

assert optimized == expected
else:
pytest.skip("no mcopy available")
9 changes: 4 additions & 5 deletions tests/unit/compiler/test_default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ 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_codegen_opt_level(optimize):
assert core._opt_gas() == (optimize == OptimizationLevel.GAS)
assert core._opt_none() == (optimize == OptimizationLevel.NONE)
assert core._opt_codesize() == (optimize == OptimizationLevel.CODESIZE)


def test_debug_mode(pytestconfig):
Expand Down
10 changes: 0 additions & 10 deletions tests/unit/compiler/test_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,6 @@
from vyper.exceptions import CompilerPanic


@pytest.fixture(params=list(opcodes.EVM_VERSIONS))
def evm_version(request):
default = opcodes.active_evm_version
try:
opcodes.active_evm_version = opcodes.EVM_VERSIONS[request.param]
yield request.param
finally:
opcodes.active_evm_version = default


def test_opcodes():
code = """
@external
Expand Down
13 changes: 4 additions & 9 deletions vyper/cli/vyper_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@
import vyper.evm.opcodes as evm
from vyper.cli import vyper_json
from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle
from vyper.compiler.settings import (
VYPER_TRACEBACK_LIMIT,
OptimizationLevel,
Settings,
_set_debug_mode,
)
from vyper.compiler.settings import VYPER_TRACEBACK_LIMIT, OptimizationLevel, Settings
from vyper.typing import ContractPath, OutputFormats

T = TypeVar("T")
Expand Down Expand Up @@ -172,9 +167,6 @@ def _parse_args(argv):

output_formats = tuple(uniq(args.format.split(",")))

if args.debug:
_set_debug_mode(True)

if args.no_optimize and args.optimize:
raise ValueError("Cannot use `--no-optimize` and `--optimize` at the same time!")

Expand All @@ -191,6 +183,9 @@ def _parse_args(argv):
if args.experimental_codegen:
settings.experimental_codegen = args.experimental_codegen

if args.debug:
settings.debug = args.debug

if args.verbose:
print(f"cli specified: `{settings}`", file=sys.stderr)

Expand Down
39 changes: 1 addition & 38 deletions vyper/codegen/core.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import contextlib
from typing import Generator

from vyper.codegen.ir_node import Encoding, IRnode
from vyper.compiler.settings import OptimizationLevel
from vyper.compiler.settings import _opt_codesize, _opt_gas, _opt_none
from vyper.evm.address_space import (
CALLDATA,
DATA,
Expand Down Expand Up @@ -916,40 +913,6 @@ def make_setter(left, right):
return _complex_make_setter(left, right)


_opt_level = OptimizationLevel.GAS


# FIXME: this is to get around the fact that we don't have a
# proper context object in the IR generation phase.
@contextlib.contextmanager
def anchor_opt_level(new_level: OptimizationLevel) -> Generator:
"""
Set the global optimization level variable for the duration of this
context manager.
"""
assert isinstance(new_level, OptimizationLevel)

global _opt_level
try:
tmp = _opt_level
_opt_level = new_level
yield
finally:
_opt_level = tmp


def _opt_codesize():
return _opt_level == OptimizationLevel.CODESIZE


def _opt_gas():
return _opt_level == OptimizationLevel.GAS


def _opt_none():
return _opt_level == OptimizationLevel.NONE


def _complex_make_setter(left, right):
if right.value == "~empty" and left.location == MEMORY:
# optimized memzero
Expand Down
6 changes: 2 additions & 4 deletions vyper/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import vyper.compiler.output as output
from vyper.compiler.input_bundle import FileInput, InputBundle, PathLike
from vyper.compiler.phases import CompilerData
from vyper.compiler.settings import Settings
from vyper.evm.opcodes import anchor_evm_version
from vyper.compiler.settings import Settings, anchor_settings
from vyper.typing import ContractPath, OutputFormats, StorageLayout

OUTPUT_FORMATS = {
Expand Down Expand Up @@ -96,7 +95,6 @@ def compile_from_file_input(
Dict
Compiler output as `{'output key': "output data"}`
"""

settings = settings or Settings()

if output_formats is None:
Expand All @@ -117,7 +115,7 @@ def compile_from_file_input(
)

ret = {}
with anchor_evm_version(compiler_data.settings.evm_version):
with anchor_settings(compiler_data.settings):
for output_format in output_formats:
if output_format not in OUTPUT_FORMATS:
raise ValueError(f"Unsupported format type {repr(output_format)}")
Expand Down
11 changes: 5 additions & 6 deletions vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@

from vyper import ast as vy_ast
from vyper.codegen import module
from vyper.codegen.core import anchor_opt_level
from vyper.codegen.ir_node import IRnode
from vyper.compiler.input_bundle import FileInput, FilesystemInputBundle, InputBundle
from vyper.compiler.settings import OptimizationLevel, Settings
from vyper.compiler.settings import OptimizationLevel, Settings, anchor_settings
from vyper.exceptions import StructureException
from vyper.ir import compile_ir, optimizer
from vyper.semantics import analyze_module, set_data_positions, validate_compilation_target
Expand Down Expand Up @@ -178,7 +177,7 @@ def global_ctx(self) -> ModuleT:
@cached_property
def _ir_output(self):
# fetch both deployment and runtime IR
return generate_ir_nodes(self.global_ctx, self.settings.optimize)
return generate_ir_nodes(self.global_ctx, self.settings)

@property
def ir_nodes(self) -> IRnode:
Expand Down Expand Up @@ -268,7 +267,7 @@ def generate_annotated_ast(vyper_module: vy_ast.Module, input_bundle: InputBundl
return vyper_module


def generate_ir_nodes(global_ctx: ModuleT, optimize: OptimizationLevel) -> tuple[IRnode, IRnode]:
def generate_ir_nodes(global_ctx: ModuleT, settings: Settings) -> tuple[IRnode, IRnode]:
"""
Generate the intermediate representation (IR) from the contextualized AST.
Expand All @@ -288,9 +287,9 @@ def generate_ir_nodes(global_ctx: ModuleT, optimize: OptimizationLevel) -> tuple
IR to generate deployment bytecode
IR to generate runtime bytecode
"""
with anchor_opt_level(optimize):
with anchor_settings(settings):
ir_nodes, ir_runtime = module.generate_ir_for_module(global_ctx)
if optimize != OptimizationLevel.NONE:
if settings.optimize != OptimizationLevel.NONE:
ir_nodes = optimizer.optimize(ir_nodes)
ir_runtime = optimizer.optimize(ir_runtime)
return ir_nodes, ir_runtime
Expand Down
Loading

0 comments on commit e4f1c24

Please sign in to comment.