diff --git a/vyper/compiler/README.md b/vyper/compiler/README.md index d6b55fdd82..eb70750a2b 100644 --- a/vyper/compiler/README.md +++ b/vyper/compiler/README.md @@ -51,11 +51,9 @@ for specific implementation details. [`vyper.compiler.compile_codes`](__init__.py) is the main user-facing function for generating compiler output from Vyper source. The process is as follows: -1. The `@evm_wrapper` decorator sets the target EVM version in -[`opcodes.py`](../evm/opcodes.py). -2. A [`CompilerData`](phases.py) object is created for each contract to be compiled. +1. A [`CompilerData`](phases.py) object is created for each contract to be compiled. This object uses `@property` methods to trigger phases of the compiler as required. -3. Functions in [`output.py`](output.py) generate the requested outputs from the +2. Functions in [`output.py`](output.py) generate the requested outputs from the compiler data. ## Design diff --git a/vyper/compiler/__init__.py b/vyper/compiler/__init__.py index 6f6e0d41ea..ca44dddf8d 100644 --- a/vyper/compiler/__init__.py +++ b/vyper/compiler/__init__.py @@ -130,20 +130,18 @@ def compile_codes( show_gas_estimates, no_bytecode_metadata, ) - _ = compiler_data._generate_ast - with anchor_evm_version(compiler_data.settings.evm_version): - for output_format in output_formats[contract_name]: - if output_format not in OUTPUT_FORMATS: - raise ValueError(f"Unsupported format type {repr(output_format)}") - try: - out.setdefault(contract_name, {}) - formatter = OUTPUT_FORMATS[output_format] - out[contract_name][output_format] = formatter(compiler_data) - except Exception as exc: - if exc_handler is not None: - exc_handler(contract_name, exc) - else: - raise exc + for output_format in output_formats[contract_name]: + if output_format not in OUTPUT_FORMATS: + raise ValueError(f"Unsupported format type {repr(output_format)}") + try: + out.setdefault(contract_name, {}) + formatter = OUTPUT_FORMATS[output_format] + out[contract_name][output_format] = formatter(compiler_data) + except Exception as exc: + if exc_handler is not None: + exc_handler(contract_name, exc) + else: + raise exc return out diff --git a/vyper/compiler/phases.py b/vyper/compiler/phases.py index a480435167..1cb513ec2e 100644 --- a/vyper/compiler/phases.py +++ b/vyper/compiler/phases.py @@ -1,4 +1,5 @@ import copy +import functools import warnings from functools import cached_property from typing import Optional, Tuple @@ -9,6 +10,7 @@ from vyper.codegen.global_context import GlobalContext from vyper.codegen.ir_node import IRnode from vyper.compiler.settings import OptimizationLevel, Settings +from vyper.evm.opcodes import anchor_evm_version from vyper.exceptions import StructureException from vyper.ir import compile_ir, optimizer from vyper.semantics import set_data_positions, validate_semantics @@ -16,6 +18,15 @@ from vyper.typing import InterfaceImports, StorageLayout +def _evm_wrapper(fn): + @functools.wraps(fn) + def inner(self, *args, **kwargs): + with anchor_evm_version(self.settings.evm_version): + return fn(self, *args, **kwargs) + + return inner + + class CompilerData: """ Object for fetching and storing compiler data for a Vyper contract. @@ -88,6 +99,8 @@ def __init__( self.no_bytecode_metadata = no_bytecode_metadata self.settings = settings or Settings() + _ = self._generate_ast # force settings to be calculated + @cached_property def _generate_ast(self): settings, ast = generate_ast(self.source_code, self.source_id, self.contract_name) @@ -125,6 +138,7 @@ def vyper_module(self): return self._generate_ast @cached_property + @_evm_wrapper def vyper_module_unfolded(self) -> vy_ast.Module: # This phase is intended to generate an AST for tooling use, and is not # used in the compilation process. @@ -132,41 +146,49 @@ def vyper_module_unfolded(self) -> vy_ast.Module: return generate_unfolded_ast(self.vyper_module, self.interface_codes) @cached_property + @_evm_wrapper def _folded_module(self): return generate_folded_ast( self.vyper_module, self.interface_codes, self.storage_layout_override ) @property + @_evm_wrapper def vyper_module_folded(self) -> vy_ast.Module: module, storage_layout = self._folded_module return module @property + @_evm_wrapper def storage_layout(self) -> StorageLayout: module, storage_layout = self._folded_module return storage_layout @property + @_evm_wrapper def global_ctx(self) -> GlobalContext: return GlobalContext(self.vyper_module_folded) @cached_property + @_evm_wrapper def _ir_output(self): # fetch both deployment and runtime IR return generate_ir_nodes(self.global_ctx, self.settings.optimize) @property + @_evm_wrapper def ir_nodes(self) -> IRnode: ir, ir_runtime = self._ir_output return ir @property + @_evm_wrapper def ir_runtime(self) -> IRnode: ir, ir_runtime = self._ir_output return ir_runtime @property + @_evm_wrapper def function_signatures(self) -> dict[str, ContractFunctionT]: # some metadata gets calculated during codegen, so # ensure codegen is run: @@ -176,23 +198,28 @@ def function_signatures(self) -> dict[str, ContractFunctionT]: return {f.name: f._metadata["type"] for f in fs} @cached_property + @_evm_wrapper def assembly(self) -> list: return generate_assembly(self.ir_nodes, self.settings.optimize) @cached_property + @_evm_wrapper def assembly_runtime(self) -> list: return generate_assembly(self.ir_runtime, self.settings.optimize) @cached_property + @_evm_wrapper def bytecode(self) -> bytes: insert_compiler_metadata = not self.no_bytecode_metadata return generate_bytecode(self.assembly, insert_compiler_metadata=insert_compiler_metadata) @cached_property + @_evm_wrapper def bytecode_runtime(self) -> bytes: return generate_bytecode(self.assembly_runtime, insert_compiler_metadata=False) @cached_property + @_evm_wrapper def blueprint_bytecode(self) -> bytes: blueprint_preamble = b"\xFE\x71\x00" # ERC5202 preamble blueprint_bytecode = blueprint_preamble + self.bytecode