def convert(expr, context): if len(expr.args) != 2: raise StructureException( "The convert function expects two parameters.", expr) arg_ast = expr.args[0] arg = Expr(arg_ast, context).ir_node out_typ = context.parse_type(expr.args[1]) if isinstance(arg.typ, BaseType): arg = unwrap_location(arg) with arg.cache_when_complex("arg") as (b, arg): if is_base_type(out_typ, "bool"): ret = to_bool(arg_ast, arg, out_typ) elif is_base_type(out_typ, "address"): ret = to_address(arg_ast, arg, out_typ) elif is_integer_type(out_typ): ret = to_int(arg_ast, arg, out_typ) elif is_bytes_m_type(out_typ): ret = to_bytes_m(arg_ast, arg, out_typ) elif is_decimal_type(out_typ): ret = to_decimal(arg_ast, arg, out_typ) elif isinstance(out_typ, ByteArrayType): ret = to_bytes(arg_ast, arg, out_typ) elif isinstance(out_typ, StringType): ret = to_string(arg_ast, arg, out_typ) else: raise StructureException(f"Conversion to {out_typ} is invalid.", arg_ast) ret = b.resolve(ret) return IRnode.from_list(ret)
def lll_for_external_call(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import pos = getpos(stmt_expr) contract_address = Expr.parse_value_expr(stmt_expr.func.value, context) value, gas, skip_contract_check = _get_special_kwargs(stmt_expr, context) args_lll = [Expr(x, context).lll_node for x in stmt_expr.args] assert isinstance(contract_address.typ, InterfaceType) contract_name = contract_address.typ.name method_name = stmt_expr.func.attr contract_sig = context.sigs[contract_name][method_name] ret = _external_call_helper( contract_address, contract_sig, args_lll, context, pos, value=value, gas=gas, skip_contract_check=skip_contract_check, ) ret.annotation = stmt_expr.get("node_source_code") return ret
def parse_Call(self): is_self_function = ((isinstance(self.stmt.func, vy_ast.Attribute)) and isinstance(self.stmt.func.value, vy_ast.Name) and self.stmt.func.value.id == "self") if isinstance(self.stmt.func, vy_ast.Name): funcname = self.stmt.func.id return STMT_DISPATCH_TABLE[funcname].build_IR( self.stmt, self.context) elif isinstance(self.stmt.func, vy_ast.Attribute) and self.stmt.func.attr in ( "append", "pop", ): darray = Expr(self.stmt.func.value, self.context).ir_node args = [Expr(x, self.context).ir_node for x in self.stmt.args] if self.stmt.func.attr == "append": assert len(args) == 1 arg = args[0] assert isinstance(darray.typ, DArrayType) assert arg.typ == darray.typ.subtype return append_dyn_array(darray, arg) else: assert len(args) == 0 return pop_dyn_array(darray, return_popped_item=False) elif is_self_function: return self_call.ir_for_self_call(self.stmt, self.context) else: return external_call.ir_for_external_call(self.stmt, self.context)
def lll_for_external_call(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import pos = getpos(stmt_expr) value, gas, skip_contract_check = _get_special_kwargs(stmt_expr, context) args_lll = [Expr(x, context).lll_node for x in stmt_expr.args] if isinstance(stmt_expr.func, vy_ast.Attribute) and isinstance( stmt_expr.func.value, vy_ast.Call): # e.g. `Foo(address).bar()` # sanity check assert len(stmt_expr.func.value.args) == 1 contract_name = stmt_expr.func.value.func.id contract_address = Expr.parse_value_expr(stmt_expr.func.value.args[0], context) elif (isinstance(stmt_expr.func.value, vy_ast.Attribute) and stmt_expr.func.value.attr in context.globals # TODO check for self? and hasattr(context.globals[stmt_expr.func.value.attr].typ, "name")): # e.g. `self.foo.bar()` # sanity check assert stmt_expr.func.value.value.id == "self", stmt_expr contract_name = context.globals[stmt_expr.func.value.attr].typ.name type_ = stmt_expr.func.value._metadata["type"] var = context.globals[stmt_expr.func.value.attr] contract_address = unwrap_location( LLLnode.from_list( type_.position.position, typ=var.typ, location="storage", pos=pos, annotation="self." + stmt_expr.func.value.attr, )) else: # TODO catch this during type checking raise StructureException("Unsupported operator.", stmt_expr) method_name = stmt_expr.func.attr contract_sig = context.sigs[contract_name][method_name] ret = _external_call_helper( contract_address, contract_sig, args_lll, context, pos, value=value, gas=gas, skip_contract_check=skip_contract_check, ) ret.annotation = stmt_expr.get("node_source_code") return ret
def parse_AugAssign(self): target = self._get_target(self.stmt.target) sub = Expr.parse_value_expr(self.stmt.value, self.context) if not isinstance(target.typ, BaseType): return if target.location == "storage": lll_node = Expr.parse_value_expr( vy_ast.BinOp( left=LLLnode.from_list(["sload", "_stloc"], typ=target.typ, pos=target.pos), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset, end_lineno=self.stmt.end_lineno, end_col_offset=self.stmt.end_col_offset, node_source_code=self.stmt.get("node_source_code"), ), self.context, ) return LLLnode.from_list( [ "with", "_stloc", target, ["sstore", "_stloc", unwrap_location(lll_node)] ], typ=None, pos=getpos(self.stmt), ) elif target.location == "memory": lll_node = Expr.parse_value_expr( vy_ast.BinOp( left=LLLnode.from_list(["mload", "_mloc"], typ=target.typ, pos=target.pos), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset, end_lineno=self.stmt.end_lineno, end_col_offset=self.stmt.end_col_offset, node_source_code=self.stmt.get("node_source_code"), ), self.context, ) return LLLnode.from_list( [ "with", "_mloc", target, ["mstore", "_mloc", unwrap_location(lll_node)] ], typ=None, pos=getpos(self.stmt), )
def ir_for_external_call(call_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import contract_address = Expr.parse_value_expr(call_expr.func.value, context) assert isinstance(contract_address.typ, InterfaceType) args_ir = [Expr(x, context).ir_node for x in call_expr.args] call_kwargs = _parse_kwargs(call_expr, context) with contract_address.cache_when_complex("external_contract") as ( b1, contract_address): return b1.resolve( _external_call_helper(contract_address, args_ir, call_kwargs, call_expr, context))
def process_arg(arg, expected_arg_type, context): # If the input value is a typestring, return the equivalent codegen type for IR generation if isinstance(expected_arg_type, TypeTypeDefinition): return new_type_to_old_type(expected_arg_type.typedef) # if it is a word type, return a stack item. # TODO: Builtins should not require value expressions if isinstance(expected_arg_type, ValueTypeDefinition) and not isinstance( expected_arg_type, (StructDefinition, ArrayValueAbstractType)): return Expr.parse_value_expr(arg, context) if isinstance(expected_arg_type, BaseTypeDefinition): return Expr(arg, context).ir_node raise CompilerPanic( f"Unexpected type: {expected_arg_type}") # pragma: notest
def parse_Assign(self): # Assignment (e.g. x[4] = y) sub = Expr(self.stmt.value, self.context).ir_node target = self._get_target(self.stmt.target) ir_node = make_setter(target, sub) return ir_node
def parse_AnnAssign(self): typ = parse_type( self.stmt.annotation, sigs=self.context.sigs, custom_structs=self.context.structs, ) varname = self.stmt.target.id pos = self.context.new_variable(varname, typ) if self.stmt.value is None: return sub = Expr(self.stmt.value, self.context).ir_node is_literal_bytes32_assign = (isinstance(sub.typ, ByteArrayType) and sub.typ.maxlen == 32 and isinstance(typ, BaseType) and typ.typ == "bytes32" and sub.typ.is_literal) # If bytes[32] to bytes32 assignment rewrite sub as bytes32. if is_literal_bytes32_assign: sub = IRnode( util.bytes_to_int(self.stmt.value.s), typ=BaseType("bytes32"), ) variable_loc = IRnode.from_list(pos, typ=typ, location=MEMORY) ir_node = make_setter(variable_loc, sub) return ir_node
def _parse_kwargs(call_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import def _bool(x): assert x.value in (0, 1), "type checker missed this" return bool(x.value) # note: codegen for kwarg values in AST order call_kwargs = { kw.arg: Expr(kw.value, context).ir_node for kw in call_expr.keywords } ret = _CallKwargs( value=unwrap_location(call_kwargs.pop("value", IRnode(0))), gas=unwrap_location(call_kwargs.pop("gas", IRnode("gas"))), skip_contract_check=_bool( call_kwargs.pop("skip_contract_check", IRnode(0))), default_return_value=call_kwargs.pop("default_return_value", None), ) if len(call_kwargs) != 0: raise TypeCheckFailure(f"Unexpected keyword arguments: {call_kwargs}") return ret
def parse_Assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: return IRnode.from_list(["assert", test_expr])
def _get_target(self, target): _dbg_expr = target if isinstance(target, vy_ast.Name) and target.id in self.context.forvars: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") if isinstance(target, vy_ast.Tuple): target = Expr(target, self.context).lll_node for node in target.args: if (node.location == "storage" and self.context.is_constant()) or not node.mutable: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") return target target = Expr.parse_pointer_expr(target, self.context) if (target.location == "storage" and self.context.is_constant()) or not target.mutable: raise TypeCheckFailure(f"Failed constancy check\n{_dbg_expr}") return target
def parse_Assign(self): # Assignment (e.g. x[4] = y) sub = Expr(self.stmt.value, self.context).lll_node target = self._get_target(self.stmt.target) lll_node = make_setter(target, sub, pos=getpos(self.stmt)) lll_node.pos = getpos(self.stmt) return lll_node
def handler_for(calldata_kwargs, default_kwargs): calldata_args = sig.base_args + calldata_kwargs # create a fake type so that get_element_ptr works calldata_args_t = TupleType(list(arg.typ for arg in calldata_args)) abi_sig = sig.abi_signature_for_kwargs(calldata_kwargs) method_id = _annotated_method_id(abi_sig) calldata_kwargs_ofst = IRnode( 4, location=CALLDATA, typ=calldata_args_t, encoding=Encoding.ABI ) # a sequence of statements to strictify kwargs into memory ret = ["seq"] # ensure calldata is at least of minimum length args_abi_t = calldata_args_t.abi_type calldata_min_size = args_abi_t.min_size() + 4 if args_abi_t.is_dynamic(): ret.append(["assert", ["ge", "calldatasize", calldata_min_size]]) else: # stricter for static data ret.append(["assert", ["eq", "calldatasize", calldata_min_size]]) # TODO optimize make_setter by using # TupleType(list(arg.typ for arg in calldata_kwargs + default_kwargs)) # (must ensure memory area is contiguous) n_base_args = len(sig.base_args) for i, arg_meta in enumerate(calldata_kwargs): k = n_base_args + i dst = context.lookup_var(arg_meta.name).pos lhs = IRnode(dst, location=MEMORY, typ=arg_meta.typ) rhs = get_element_ptr(calldata_kwargs_ofst, k, array_bounds_check=False) copy_arg = make_setter(lhs, rhs) copy_arg.source_pos = getpos(arg_meta.ast_source) ret.append(copy_arg) for x in default_kwargs: dst = context.lookup_var(x.name).pos lhs = IRnode(dst, location=MEMORY, typ=x.typ) lhs.source_pos = getpos(x.ast_source) kw_ast_val = sig.default_values[x.name] # e.g. `3` in x: int = 3 rhs = Expr(kw_ast_val, context).ir_node copy_arg = make_setter(lhs, rhs) copy_arg.source_pos = getpos(x.ast_source) ret.append(copy_arg) ret.append(["goto", sig.external_function_base_entry_label]) ret = ["if", ["eq", "_calldata_method_id", method_id], ret] return ret
def _get_special_kwargs(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import value, gas, skip_contract_check = None, None, None for kw in stmt_expr.keywords: if kw.arg == "gas": gas = Expr.parse_value_expr(kw.value, context) elif kw.arg == "value": value = Expr.parse_value_expr(kw.value, context) elif kw.arg == "skip_contract_check": skip_contract_check = kw.value.value assert isinstance(skip_contract_check, bool), "type checker missed this" else: raise TypeCheckFailure("Unexpected keyword argument") # TODO maybe return a small dataclass to reduce verbosity return value, gas, skip_contract_check
def _assert_reason(self, test_expr, msg): if isinstance(msg, vy_ast.Name) and msg.id == "UNREACHABLE": return LLLnode.from_list(["assert_unreachable", test_expr], typ=None, pos=getpos(msg)) # set constant so that revert reason str is well behaved try: tmp = self.context.constancy self.context.constancy = Constancy.Constant msg_lll = Expr(msg, self.context).lll_node finally: self.context.constancy = tmp # TODO this is probably useful in codegen.core # compare with eval_seq. def _get_last(lll): if len(lll.args) == 0: return lll.value return _get_last(lll.args[-1]) # TODO maybe use ensure_in_memory if msg_lll.location != "memory": buf = self.context.new_internal_variable(msg_lll.typ) instantiate_msg = make_byte_array_copier(buf, msg_lll) else: buf = _get_last(msg_lll) if not isinstance(buf, int): raise CompilerPanic(f"invalid bytestring {buf}\n{self}") instantiate_msg = msg_lll # offset of bytes in (bytes,) method_id = util.abi_method_id("Error(string)") # abi encode method_id + bytestring assert buf >= 36, "invalid buffer" # we don't mind overwriting other memory because we are # getting out of here anyway. _runtime_length = ["mload", buf] revert_seq = [ "seq", instantiate_msg, zero_pad(buf), ["mstore", buf - 64, method_id], ["mstore", buf - 32, 0x20], [ "revert", buf - 36, ["add", 4 + 32 + 32, ["ceil32", _runtime_length]] ], ] if test_expr is not None: lll_node = ["if", ["iszero", test_expr], revert_seq] else: lll_node = revert_seq return LLLnode.from_list(lll_node, typ=None, pos=getpos(self.stmt))
def _parse_For_list(self): with self.context.range_scope(): iter_list = Expr(self.stmt.iter, self.context).lll_node # override with type inferred at typechecking time # TODO investigate why stmt.target.type != stmt.iter.type.subtype target_type = new_type_to_old_type(self.stmt.target._metadata["type"]) iter_list.typ.subtype = target_type # user-supplied name for loop variable varname = self.stmt.target.id loop_var = LLLnode.from_list( self.context.new_variable(varname, target_type), typ=target_type, location="memory", ) i = LLLnode.from_list(self.context.fresh_varname("for_list_ix"), typ="uint256") self.context.forvars[varname] = True ret = ["seq"] # list literal, force it to memory first if isinstance(self.stmt.iter, vy_ast.List): tmp_list = LLLnode.from_list( self.context.new_internal_variable(iter_list.typ), typ=iter_list.typ, location="memory", ) ret.append(make_setter(tmp_list, iter_list, pos=getpos(self.stmt))) iter_list = tmp_list # set up the loop variable loop_var_ast = getpos(self.stmt.target) e = get_element_ptr(iter_list, i, array_bounds_check=False, pos=loop_var_ast) body = [ "seq", make_setter(loop_var, e, pos=loop_var_ast), parse_body(self.stmt.body, self.context), ] repeat_bound = iter_list.typ.count if isinstance(iter_list.typ, DArrayType): array_len = get_dyn_array_count(iter_list) else: array_len = repeat_bound ret.append(["repeat", i, 0, array_len, repeat_bound, body]) del self.context.forvars[varname] return LLLnode.from_list(ret, pos=getpos(self.stmt))
def convert(expr, context): if len(expr.args) != 2: raise StructureException( "The convert function expects two parameters.", expr) arg_ast = expr.args[0] arg = Expr(arg_ast, context).ir_node original_arg = arg out_typ = context.parse_type(expr.args[1]) if isinstance(arg.typ, BaseType): arg = unwrap_location(arg) with arg.cache_when_complex("arg") as (b, arg): if is_base_type(out_typ, "bool"): ret = to_bool(arg_ast, arg, out_typ) elif is_base_type(out_typ, "address"): ret = to_address(arg_ast, arg, out_typ) elif isinstance(out_typ, EnumType): ret = to_enum(arg_ast, arg, out_typ) elif is_integer_type(out_typ): ret = to_int(arg_ast, arg, out_typ) elif is_bytes_m_type(out_typ): ret = to_bytes_m(arg_ast, arg, out_typ) elif is_decimal_type(out_typ): ret = to_decimal(arg_ast, arg, out_typ) elif isinstance(out_typ, ByteArrayType): ret = to_bytes(arg_ast, arg, out_typ) elif isinstance(out_typ, StringType): ret = to_string(arg_ast, arg, out_typ) else: raise StructureException(f"Conversion to {out_typ} is invalid.", arg_ast) # test if arg actually changed. if not, we do not need to use # unwrap_location (this can reduce memory traffic for downstream # operations which are in-place, like the returndata routine) test_arg = IRnode.from_list(arg, typ=out_typ) if test_arg == ret: original_arg.typ = out_typ return original_arg return IRnode.from_list(b.resolve(ret))
def parse_If(self): if self.stmt.orelse: with self.context.block_scope(): add_on = [parse_body(self.stmt.orelse, self.context)] else: add_on = [] with self.context.block_scope(): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) body = ["if", test_expr, parse_body(self.stmt.body, self.context)] + add_on lll_node = LLLnode.from_list(body, typ=None, pos=getpos(self.stmt)) return lll_node
def parse_AugAssign(self): target = self._get_target(self.stmt.target) sub = Expr.parse_value_expr(self.stmt.value, self.context) if not isinstance(target.typ, BaseType): return with target.cache_when_complex("_loc") as (b, target): rhs = Expr.parse_value_expr( vy_ast.BinOp( left=IRnode.from_list(LOAD(target), typ=target.typ), right=sub, op=self.stmt.op, lineno=self.stmt.lineno, col_offset=self.stmt.col_offset, end_lineno=self.stmt.end_lineno, end_col_offset=self.stmt.end_col_offset, node_source_code=self.stmt.get("node_source_code"), ), self.context, ) return b.resolve(STORE(target, rhs))
def _parse_For_range(self): # attempt to use the type specified by type checking, fall back to `int256` # this is a stopgap solution to allow uint256 - it will be properly solved # once we refactor type system iter_typ = "int256" if "type" in self.stmt.target._metadata: iter_typ = self.stmt.target._metadata["type"]._id # Get arg0 arg0 = self.stmt.iter.args[0] num_of_args = len(self.stmt.iter.args) # Type 1 for, e.g. for i in range(10): ... if num_of_args == 1: arg0_val = self._get_range_const_value(arg0) start = IRnode.from_list(0, typ=iter_typ) rounds = arg0_val # Type 2 for, e.g. for i in range(100, 110): ... elif self._check_valid_range_constant(self.stmt.iter.args[1], raise_exception=False)[0]: arg0_val = self._get_range_const_value(arg0) arg1_val = self._get_range_const_value(self.stmt.iter.args[1]) start = IRnode.from_list(arg0_val, typ=iter_typ) rounds = IRnode.from_list(arg1_val - arg0_val, typ=iter_typ) # Type 3 for, e.g. for i in range(x, x + 10): ... else: arg1 = self.stmt.iter.args[1] rounds = self._get_range_const_value(arg1.right) start = Expr.parse_value_expr(arg0, self.context) r = rounds if isinstance(rounds, int) else rounds.value if r < 1: return varname = self.stmt.target.id i = IRnode.from_list(self.context.fresh_varname("range_ix"), typ="uint256") iptr = self.context.new_variable(varname, BaseType(iter_typ)) self.context.forvars[varname] = True loop_body = ["seq"] # store the current value of i so it is accessible to userland loop_body.append(["mstore", iptr, i]) loop_body.append(parse_body(self.stmt.body, self.context)) ir_node = IRnode.from_list( ["repeat", i, start, rounds, rounds, loop_body]) del self.context.forvars[varname] return ir_node
def parse_If(self): if self.stmt.orelse: with self.context.block_scope(): add_on = [parse_body(self.stmt.orelse, self.context)] else: add_on = [] with self.context.block_scope(): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) body = ["if", test_expr, parse_body(self.stmt.body, self.context)] + add_on ir_node = IRnode.from_list(body) return ir_node
def parse_Assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if test_expr.typ.is_literal: if test_expr.value == 1: # skip literal assertions that always pass return LLLnode.from_list(["pass"], typ=None, pos=getpos(self.stmt)) else: test_expr = test_expr.value if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: return LLLnode.from_list(["assert", test_expr], typ=None, pos=getpos(self.stmt))
def parse_Log(self): event = self.stmt._metadata["type"] args = [Expr(arg, self.context).lll_node for arg in self.stmt.value.args] topic_lll = [] data_lll = [] for arg, is_indexed in zip(args, event.indexed): if is_indexed: topic_lll.append(arg) else: data_lll.append(arg) return events.lll_node_for_log(self.stmt, event, topic_lll, data_lll, self.context)
def _check_valid_range_constant(self, arg_ast_node, raise_exception=True): with self.context.range_scope(): # TODO should catch if raise_exception == False? arg_expr = Expr.parse_value_expr(arg_ast_node, self.context) is_integer_literal = (isinstance(arg_expr.typ, BaseType) and arg_expr.typ.is_literal and arg_expr.typ.typ in {"uint256", "int256"}) if not is_integer_literal and raise_exception: raise StructureException( "Range only accepts literal (constant) values of type uint256 or int256", arg_ast_node, ) return is_integer_literal, arg_expr
def parse_Assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) if test_expr.typ.is_literal: if test_expr.value == 1: # skip literal assertions that always pass # TODO move this to optimizer return IRnode.from_list(["pass"]) else: test_expr = test_expr.value if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: return IRnode.from_list(["assert", test_expr])
def parse_Call(self): # TODO use expr.func.type.is_internal once type annotations # are consistently available. is_self_function = ((isinstance(self.stmt.func, vy_ast.Attribute)) and isinstance(self.stmt.func.value, vy_ast.Name) and self.stmt.func.value.id == "self") if isinstance(self.stmt.func, vy_ast.Name): funcname = self.stmt.func.id return STMT_DISPATCH_TABLE[funcname].build_IR( self.stmt, self.context) elif isinstance(self.stmt.func, vy_ast.Attribute) and self.stmt.func.attr in ( "append", "pop", ): # TODO: consider moving this to builtins darray = Expr(self.stmt.func.value, self.context).ir_node args = [Expr(x, self.context).ir_node for x in self.stmt.args] if self.stmt.func.attr == "append": # sanity checks assert len(args) == 1 arg = args[0] assert isinstance(darray.typ, DArrayType) check_assign(dummy_node_for_type(darray.typ.subtype), dummy_node_for_type(arg.typ)) return append_dyn_array(darray, arg) else: assert len(args) == 0 return pop_dyn_array(darray, return_popped_item=False) elif is_self_function: return self_call.ir_for_self_call(self.stmt, self.context) else: return external_call.ir_for_external_call(self.stmt, self.context)
def handler_for(calldata_kwargs, default_kwargs): calldata_args = sig.base_args + calldata_kwargs # create a fake type so that get_element_ptr works calldata_args_t = TupleType(list(arg.typ for arg in calldata_args)) abi_sig = sig.abi_signature_for_kwargs(calldata_kwargs) method_id = _annotated_method_id(abi_sig) calldata_kwargs_ofst = LLLnode(4, location="calldata", typ=calldata_args_t, encoding=Encoding.ABI) # a sequence of statements to strictify kwargs into memory ret = ["seq"] # TODO optimize make_setter by using # TupleType(list(arg.typ for arg in calldata_kwargs + default_kwargs)) # (must ensure memory area is contiguous) n_base_args = len(sig.base_args) for i, arg_meta in enumerate(calldata_kwargs): k = n_base_args + i dst = context.lookup_var(arg_meta.name).pos lhs = LLLnode(dst, location="memory", typ=arg_meta.typ) rhs = get_element_ptr(calldata_kwargs_ofst, k, pos=None, array_bounds_check=False) ret.append(make_setter(lhs, rhs, pos)) for x in default_kwargs: dst = context.lookup_var(x.name).pos lhs = LLLnode(dst, location="memory", typ=x.typ) kw_ast_val = sig.default_values[x.name] # e.g. `3` in x: int = 3 rhs = Expr(kw_ast_val, context).lll_node ret.append(make_setter(lhs, rhs, pos)) ret.append(["goto", sig.external_function_base_entry_label]) ret = ["if", ["eq", "_calldata_method_id", method_id], ret] return ret
def lll_for_self_call(stmt_expr, context): from vyper.codegen.expr import Expr # TODO rethink this circular import pos = getpos(stmt_expr) # ** Internal Call ** # Steps: # - copy arguments into the soon-to-be callee # - allocate return buffer # - push jumpdest (callback ptr) and return buffer location # - jump to label # - (private function will fill return buffer and jump back) method_name = stmt_expr.func.attr pos_args_lll = [Expr(x, context).lll_node for x in stmt_expr.args] sig, kw_vals = context.lookup_internal_function(method_name, pos_args_lll) kw_args_lll = [Expr(x, context).lll_node for x in kw_vals] args_lll = pos_args_lll + kw_args_lll args_tuple_t = TupleType([x.typ for x in args_lll]) args_as_tuple = LLLnode.from_list(["multi"] + [x for x in args_lll], typ=args_tuple_t) # register callee to help calculate our starting frame offset context.register_callee(sig.frame_size) if context.is_constant() and sig.mutability not in ("view", "pure"): raise StateAccessViolation( f"May not call state modifying function " f"'{method_name}' within {context.pp_constancy()}.", getpos(stmt_expr), ) # TODO move me to type checker phase if not sig.internal: raise StructureException("Cannot call external functions via 'self'", stmt_expr) return_label = _generate_label(f"{sig.internal_function_label}_call") # allocate space for the return buffer # TODO allocate in stmt and/or expr.py if sig.return_type is not None: return_buffer = LLLnode.from_list( context.new_internal_variable(sig.return_type), annotation=f"{return_label}_return_buf" ) else: return_buffer = None # note: dst_tuple_t != args_tuple_t dst_tuple_t = TupleType([arg.typ for arg in sig.args]) args_dst = LLLnode(sig.frame_start, typ=dst_tuple_t, location="memory") # if one of the arguments is a self call, the argument # buffer could get borked. to prevent against that, # write args to a temporary buffer until all the arguments # are fully evaluated. if args_as_tuple.contains_self_call: copy_args = ["seq"] # TODO deallocate me tmp_args_buf = LLLnode( context.new_internal_variable(dst_tuple_t), typ=dst_tuple_t, location="memory", ) copy_args.append( # --> args evaluate here <-- make_setter(tmp_args_buf, args_as_tuple, pos) ) copy_args.append(make_setter(args_dst, tmp_args_buf, pos)) else: copy_args = make_setter(args_dst, args_as_tuple, pos) goto_op = ["goto", sig.internal_function_label] # pass return buffer to subroutine if return_buffer is not None: goto_op += [return_buffer] # pass return label to subroutine goto_op += [push_label_to_stack(return_label)] call_sequence = [ "seq", copy_args, goto_op, ["label", return_label, ["var_list"], "pass"], ] if return_buffer is not None: # push return buffer location to stack call_sequence += [return_buffer] o = LLLnode.from_list( call_sequence, typ=sig.return_type, location="memory", pos=pos, annotation=stmt_expr.get("node_source_code"), add_gas_estimate=sig.gas, ) o.is_self_call = True return o
def parse_Return(self): ir_val = None if self.stmt.value is not None: ir_val = Expr(self.stmt.value, self.context).ir_node return make_return_stmt(ir_val, self.stmt, self.context)