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

refactor[codegen]: make settings into a global object #3929

Merged
merged 16 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
41 changes: 25 additions & 16 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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.compiler.settings import OptimizationLevel, Settings, set_global_settings
from vyper.evm.opcodes import version_check
from vyper.exceptions import EvmVersionException
from vyper.ir import compile_ir, optimizer
Expand Down Expand Up @@ -81,17 +81,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 +101,27 @@ 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):
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 +145,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
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
5 changes: 2 additions & 3 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 @@ -117,7 +116,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
61 changes: 53 additions & 8 deletions vyper/compiler/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import contextlib
import os
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from typing import Generator, Optional

VYPER_COLOR_OUTPUT = os.environ.get("VYPER_COLOR_OUTPUT", "0") == "1"
VYPER_ERROR_CONTEXT_LINES = int(os.environ.get("VYPER_ERROR_CONTEXT_LINES", "1"))
Expand Down Expand Up @@ -43,16 +44,60 @@ class Settings:
optimize: Optional[OptimizationLevel] = None
evm_version: Optional[str] = None
experimental_codegen: Optional[bool] = None
debug: Optional[bool] = None


_DEBUG = False
_settings = None


def _is_debug_mode():
global _DEBUG
return _DEBUG
def get_global_settings() -> Optional[Settings]:
charles-cooper marked this conversation as resolved.
Show resolved Hide resolved
return _settings


def set_global_settings(new_settings: Optional[Settings]) -> None:
assert isinstance(new_settings, Settings) or new_settings is None
# TODO evil circular import
from vyper.evm.opcodes import EVM_VERSIONS, set_global_evm_version
Fixed Show fixed Hide fixed

global _settings
_settings = new_settings

# set the global evm version so that version_check picks it up.
# this is a bit spooky, but it's generally always what we want
# when set_global_settings is called.
if new_settings is not None and new_settings.evm_version is not None:
evm_version = new_settings.evm_version
set_global_evm_version(EVM_VERSIONS[evm_version])


# could maybe refactor this, but it is easier for now than threading settings
# around everywhere.
@contextlib.contextmanager
def anchor_settings(new_settings: Settings) -> Generator:
"""
Set the globally available settings for the duration of this context manager
"""
assert new_settings is not None
global _settings
try:
tmp = get_global_settings()
set_global_settings(new_settings)
yield
finally:
set_global_settings(tmp)


def _set_debug_mode(dbg: bool = False) -> None:
global _DEBUG
_DEBUG = dbg
def _opt_codesize():
return _settings.optimize == OptimizationLevel.CODESIZE


def _opt_gas():
return _settings.optimize == OptimizationLevel.GAS


def _opt_none():
return _settings.optimize == OptimizationLevel.NONE


def _is_debug_mode():
return get_global_settings().debug
Loading
Loading