def generate_inline_function(code, variables, variables_2, memory_allocator): ast_code = parse_to_ast(code) # Annotate the AST with a temporary old (i.e. typecheck) namespace namespace = Namespace() namespace.update(variables_2) with override_global_namespace(namespace): # Initialise a placeholder `FunctionDef` AST node and corresponding # `ContractFunction` type to rely on the annotation visitors in semantics # module. fn_node = vy_ast.FunctionDef() fn_node.body = [] fn_node.args = vy_ast.arguments(defaults=[]) fn_node._metadata["type"] = ContractFunction( "sqrt_builtin", {}, 0, 0, None, FunctionVisibility.INTERNAL, StateMutability.NONPAYABLE) sv = FunctionNodeVisitor(ast_code, fn_node, namespace) for n in ast_code.body: sv.visit(n) new_context = Context(vars_=variables, global_ctx=GlobalContext(), memory_allocator=memory_allocator) generated_ir = parse_body(ast_code.body, new_context) # strip source position info from the generated_ir since # it doesn't make any sense (e.g. the line numbers will start from 0 # instead of where we are in the code) # NOTE if we ever use this for inlining user-code, it would make # sense to fix the offsets of the source positions in the generated # code instead of stripping them. _strip_source_pos(generated_ir) return new_context, generated_ir
def generate_lll_for_internal_function(code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context) -> LLLnode: """ Parse a internal function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :param context: current calling context :return: function body in LLL """ # The calling convention is: # Caller fills in argument buffer # Caller provides return address, return buffer on the stack # Callee runs its code, fills in return buffer provided by caller # Callee jumps back to caller # The reason caller fills argument buffer is so there is less # complication with passing args on the stack; the caller is better # suited to optimize the copy operation. Also it avoids the callee # having to handle default args; that is easier left to the caller # as well. Meanwhile, the reason the callee fills the return buffer # is first, similarly, the callee is more suited to optimize the copy # operation. Second, it allows the caller to allocate the return # buffer in a way which reduces the number of copies. Third, it # reduces the potential for bugs since it forces the caller to have # the return data copied into a preallocated location. Otherwise, a # situation like the following is easy to bork: # x: T[2] = [self.generate_T(), self.generate_T()] func_type = code._metadata["type"] # Get nonreentrant lock for arg in sig.args: # allocate a variable for every arg, setting mutability # to False to comply with vyper semantics, function arguments are immutable context.new_variable(arg.name, arg.typ, is_mutable=False) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) function_entry_label = sig.internal_function_label cleanup_label = sig.exit_sequence_label # jump to the label which was passed in via stack stop_func = LLLnode.from_list(["jump", "pass"], annotation="jump to return address") enter = [["label", function_entry_label]] + nonreentrant_pre body = [parse_body(c, context) for c in code.body] exit = [["label", cleanup_label]] + nonreentrant_post + [stop_func] return LLLnode.from_list( ["seq"] + enter + body + exit, typ=None, pos=getpos(code), )
def generate_lll_for_external_function(code, sig, context, check_nonpayable): # TODO type hints: # def generate_lll_for_external_function( # code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context, check_nonpayable: bool, # ) -> LLLnode: """Return the LLL for an external function. Includes code to inspect the method_id, enter the function (nonpayable and reentrancy checks), handle kwargs and exit the function (clean up reentrancy storage variables) """ func_type = code._metadata["type"] pos = getpos(code) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) # generate handlers for base args and register the variable records handle_base_args = _register_function_args(context, sig) # generate handlers for kwargs and register the variable records kwarg_handlers = _generate_kwarg_handlers(context, sig, pos) # once optional args have been handled, # generate the main body of the function entrance = [["label", sig.external_function_base_entry_label]] entrance += handle_base_args if check_nonpayable and sig.mutability != "payable": # if the contract contains payable functions, but this is not one of them # add an assertion that the value of the call is zero entrance += [["assert", ["iszero", "callvalue"]]] entrance += nonreentrant_pre body = [parse_body(c, context) for c in code.body] exit = [["label", sig.exit_sequence_label]] + nonreentrant_post if sig.is_init_func: pass # init func has special exit sequence generated by module.py elif context.return_type is None: exit += [["stop"]] else: # ret_ofst and ret_len stack items passed by function body; consume using 'pass' exit += [["return", "pass", "pass"]] # the lll which comprises the main body of the function, # besides any kwarg handling func_common_lll = ["seq"] + entrance + body + exit if sig.is_default_func or sig.is_init_func: # default and init funcs have special entries generated by module.py ret = func_common_lll else: ret = kwarg_handlers # sneak the base code into the kwarg handler # TODO rethink this / make it clearer ret[-1][-1].append(func_common_lll) return LLLnode.from_list(ret, pos=getpos(code))
def generate_inline_function(code, variables, memory_allocator): ast_code = parse_to_ast(code) new_context = Context(vars_=variables, global_ctx=GlobalContext(), memory_allocator=memory_allocator) generated_ir = parse_body(ast_code.body, new_context) # strip source position info from the generated_ir since # it doesn't make any sense (e.g. the line numbers will start from 0 # instead of where we are in the code) # NOTE if we ever use this for inlining user-code, it would make # sense to fix the offsets of the source positions in the generated # code instead of stripping them. _strip_source_pos(generated_ir) return new_context, generated_ir
def generate_lll_for_external_function(code, sig, context, check_nonpayable): # TODO type hints: # def generate_lll_for_external_function( # code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context, check_nonpayable: bool, # ) -> LLLnode: """Return the LLL for an external function. Includes code to inspect the method_id, enter the function (nonpayable and reentrancy checks), handle kwargs and exit the function (clean up reentrancy storage variables) """ func_type = code._metadata["type"] pos = getpos(code) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) # generate handlers for base args and register the variable records handle_base_args = _register_function_args(context, sig) # generate handlers for kwargs and register the variable records kwarg_handlers = _generate_kwarg_handlers(context, sig, pos) body = ["seq"] # once optional args have been handled, # generate the main body of the function body += handle_base_args if check_nonpayable and sig.mutability != "payable": # if the contract contains payable functions, but this is not one of them # add an assertion that the value of the call is zero body += [["assert", ["iszero", "callvalue"]]] body += nonreentrant_pre body += [parse_body(code.body, context, ensure_terminated=True)] # wrap the body in labeled block body = ["label", sig.external_function_base_entry_label, ["var_list"], body] exit_sequence = ["seq"] + nonreentrant_post if sig.is_init_func: pass # init func has special exit sequence generated by module.py elif context.return_type is None: exit_sequence += [["stop"]] else: exit_sequence += [["return", "ret_ofst", "ret_len"]] exit_sequence_args = ["var_list"] if context.return_type is not None: exit_sequence_args += ["ret_ofst", "ret_len"] # wrap the exit in a labeled block exit = ["label", sig.exit_sequence_label, exit_sequence_args, exit_sequence] # the lll which comprises the main body of the function, # besides any kwarg handling func_common_lll = ["seq", body, exit] if sig.is_default_func or sig.is_init_func: ret = ["seq"] # add a goto to make the function entry look like other functions # (for zksync interpreter) ret.append(["goto", sig.external_function_base_entry_label]) ret.append(func_common_lll) else: ret = kwarg_handlers # sneak the base code into the kwarg handler # TODO rethink this / make it clearer ret[-1][-1].append(func_common_lll) return LLLnode.from_list(ret, pos=getpos(code))
def generate_ir_for_internal_function( code: vy_ast.FunctionDef, sig: FunctionSignature, context: Context ) -> IRnode: """ Parse a internal function (FuncDef), and produce full function body. :param sig: the FuntionSignature :param code: ast of function :param context: current calling context :return: function body in IR """ # The calling convention is: # Caller fills in argument buffer # Caller provides return address, return buffer on the stack # Callee runs its code, fills in return buffer provided by caller # Callee jumps back to caller # The reason caller fills argument buffer is so there is less # complication with passing args on the stack; the caller is better # suited to optimize the copy operation. Also it avoids the callee # having to handle default args; that is easier left to the caller # as well. Meanwhile, the reason the callee fills the return buffer # is first, similarly, the callee is more suited to optimize the copy # operation. Second, it allows the caller to allocate the return # buffer in a way which reduces the number of copies. Third, it # reduces the potential for bugs since it forces the caller to have # the return data copied into a preallocated location. Otherwise, a # situation like the following is easy to bork: # x: T[2] = [self.generate_T(), self.generate_T()] func_type = code._metadata["type"] # Get nonreentrant lock for arg in sig.args: # allocate a variable for every arg, setting mutability # to False to comply with vyper semantics, function arguments are immutable context.new_variable(arg.name, arg.typ, is_mutable=False) nonreentrant_pre, nonreentrant_post = get_nonreentrant_lock(func_type) function_entry_label = sig.internal_function_label cleanup_label = sig.exit_sequence_label stack_args = ["var_list"] if func_type.return_type: stack_args += ["return_buffer"] stack_args += ["return_pc"] body = [ "label", function_entry_label, stack_args, ["seq"] + nonreentrant_pre + [parse_body(code.body, context, ensure_terminated=True)], ] cleanup_routine = [ "label", cleanup_label, ["var_list", "return_pc"], ["seq"] + nonreentrant_post + [["exit_to", "return_pc"]], ] return IRnode.from_list(["seq", body, cleanup_routine])