diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index ffeb5b7299..a0da28e118 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -97,6 +97,7 @@ def __init__( state_mutability: StateMutability, from_interface: bool = False, nonreentrant: bool = False, + inline: bool = False, ast_def: Optional[vy_ast.VyperNode] = None, ) -> None: super().__init__() @@ -109,6 +110,7 @@ def __init__( self.mutability = state_mutability self.nonreentrant = nonreentrant self.from_interface = from_interface + self.inline = inline # sanity check, nonreentrant used to be Optional[str] assert isinstance(self.nonreentrant, bool) @@ -250,6 +252,7 @@ def from_abi(cls, abi: dict) -> "ContractFunctionT": from_interface=True, function_visibility=FunctionVisibility.EXTERNAL, state_mutability=StateMutability.from_abi(abi), + inline=False, ) @classmethod @@ -309,6 +312,7 @@ def from_InterfaceDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": state_mutability, from_interface=True, nonreentrant=False, + inline=False, ast_def=funcdef, ) @@ -327,14 +331,14 @@ def from_vyi(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": ------- ContractFunctionT """ - function_visibility, state_mutability, nonreentrant = _parse_decorators(funcdef) + function_visibility, state_mutability, nonreentrant, inline = _parse_decorators(funcdef) - if nonreentrant: + if nonreentrant or inline: # TODO: refactor so parse_decorators returns the AST location - decorator = next(d for d in funcdef.decorator_list if d.id == "nonreentrant") - raise FunctionDeclarationException( - "`@nonreentrant` not allowed in interfaces", decorator + decorator = next( + d for d in funcdef.decorator_list if d.id in ("nonreentrant", "inline") ) + raise FunctionDeclarationException(f"`@{d.id}` not allowed in interfaces", decorator) # it's redundant to specify visibility in vyi - always should be external if function_visibility is None: @@ -378,6 +382,7 @@ def from_vyi(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": state_mutability, from_interface=True, nonreentrant=nonreentrant, + inline=False, ast_def=funcdef, ) @@ -395,7 +400,7 @@ def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": ------- ContractFunctionT """ - function_visibility, state_mutability, nonreentrant = _parse_decorators(funcdef) + function_visibility, state_mutability, nonreentrant, inline = _parse_decorators(funcdef) # it's redundant to specify internal visibility - it's implied by not being external if function_visibility is None: @@ -453,6 +458,7 @@ def from_FunctionDef(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": state_mutability, from_interface=False, nonreentrant=nonreentrant, + inline=inline, ast_def=funcdef, ) @@ -726,10 +732,11 @@ def _parse_return_type(funcdef: vy_ast.FunctionDef) -> Optional[VyperType]: def _parse_decorators( funcdef: vy_ast.FunctionDef, -) -> tuple[Optional[FunctionVisibility], StateMutability, bool]: +) -> tuple[Optional[FunctionVisibility], StateMutability, bool, bool]: function_visibility = None state_mutability = None nonreentrant_node = None + inline_node = None for decorator in funcdef.decorator_list: if isinstance(decorator, vy_ast.Call): @@ -747,6 +754,12 @@ def _parse_decorators( nonreentrant_node = decorator + elif decorator.get("id") == "inline": + if inline_node is not None: + raise StructureException("inline decorator is already set", inline_node) + + inline_node = decorator + elif isinstance(decorator, vy_ast.Name): if FunctionVisibility.is_valid_value(decorator.id): if function_visibility is not None: @@ -786,7 +799,8 @@ def _parse_decorators( raise StructureException("Cannot use reentrancy guard on pure functions", nonreentrant_node) nonreentrant = nonreentrant_node is not None - return function_visibility, state_mutability, nonreentrant + inline = inline_node is not None + return function_visibility, state_mutability, nonreentrant, inline def _parse_args( diff --git a/vyper/venom/context.py b/vyper/venom/context.py index 55be7d2f61..f195af6ae7 100644 --- a/vyper/venom/context.py +++ b/vyper/venom/context.py @@ -60,10 +60,10 @@ def add_function(self, fn: IRFunction) -> None: def remove_function(self, fn: IRFunction) -> None: del self.functions[fn.name] - def create_function(self, name: str) -> IRFunction: + def create_function(self, name: str, inline: bool = False) -> IRFunction: label = IRLabel(name, True) assert label not in self.functions, f"duplicate function {label}" - fn = IRFunction(label, self) + fn = IRFunction(label, self, inline=inline) self.add_function(fn) return fn diff --git a/vyper/venom/function.py b/vyper/venom/function.py index d2148dee05..bdc32abd0a 100644 --- a/vyper/venom/function.py +++ b/vyper/venom/function.py @@ -26,16 +26,18 @@ class IRFunction: ctx: "IRContext" # type: ignore # noqa: F821 args: list last_variable: int + inline: bool _basic_block_dict: dict[str, IRBasicBlock] # Used during code generation _ast_source_stack: list[IRnode] _error_msg_stack: list[str] - def __init__(self, name: IRLabel, ctx: "IRContext" = None) -> None: # type: ignore # noqa: F821 + def __init__(self, name: IRLabel, ctx: "IRContext" = None, inline: bool = False) -> None: # type: ignore # noqa: F821 self.ctx = ctx self.name = name self.args = [] + self.inline = inline self._basic_block_dict = {} self.last_variable = 0 diff --git a/vyper/venom/ir_node_to_venom.py b/vyper/venom/ir_node_to_venom.py index accbafd2cf..0b9ee24eb3 100644 --- a/vyper/venom/ir_node_to_venom.py +++ b/vyper/venom/ir_node_to_venom.py @@ -198,7 +198,10 @@ def _handle_internal_func( ) -> IRFunction: global _alloca_table - fn = fn.ctx.create_function(ir.args[0].args[0].value) + func_t = ir.passthrough_metadata["func_t"] + inline = func_t.inline + + fn = fn.ctx.create_function(ir.args[0].args[0].value, inline=inline) bb = fn.get_basic_block() diff --git a/vyper/venom/passes/function_inliner.py b/vyper/venom/passes/function_inliner.py index 616c74c039..992d4d373d 100644 --- a/vyper/venom/passes/function_inliner.py +++ b/vyper/venom/passes/function_inliner.py @@ -70,6 +70,10 @@ def _select_inline_candidate(self) -> Optional[IRFunction]: if call_count == 1: return func + # always inline if it is requested + if func.inline: + return func + # Decide whether to inline based on the optimization level. if self.optimize == OptimizationLevel.CODESIZE: continue