diff --git a/mypyc/analysis/capsule_deps.py b/mypyc/analysis/capsule_deps.py index bcfa40047ce0..5b39b8bd2e10 100644 --- a/mypyc/analysis/capsule_deps.py +++ b/mypyc/analysis/capsule_deps.py @@ -1,9 +1,10 @@ from __future__ import annotations +from mypyc.ir.class_ir import ClassIR from mypyc.ir.deps import Dependency from mypyc.ir.func_ir import FuncIR from mypyc.ir.ops import Assign, CallC, PrimitiveOp -from mypyc.ir.rtypes import RStruct, RTuple, RType, RUnion +from mypyc.ir.rtypes import RStruct, RTuple, RType, RUnion, RVec def find_implicit_op_dependencies(fn: FuncIR) -> set[Dependency] | None: @@ -48,6 +49,15 @@ def find_type_dependencies(fn: FuncIR, deps: set[Dependency] | None) -> set[Depe return deps +def find_class_dependencies(cl: ClassIR) -> set[Dependency] | None: + """Find dependencies from class attribute types.""" + deps: set[Dependency] | None = None + for base in cl.mro: + for attr_type in base.attributes.values(): + deps = collect_type_deps(attr_type, deps) + return deps + + def collect_type_deps(typ: RType, deps: set[Dependency] | None) -> set[Dependency] | None: """Collect dependencies from an RType, recursively checking compound types.""" if typ.dependencies is not None: @@ -64,4 +74,6 @@ def collect_type_deps(typ: RType, deps: set[Dependency] | None) -> set[Dependenc elif isinstance(typ, RStruct): for item in typ.types: deps = collect_type_deps(item, deps) + elif isinstance(typ, RVec): + deps = collect_type_deps(typ.item_type, deps) return deps diff --git a/mypyc/analysis/dataflow.py b/mypyc/analysis/dataflow.py index 8b6b1d52c988..2a916f426f41 100644 --- a/mypyc/analysis/dataflow.py +++ b/mypyc/analysis/dataflow.py @@ -197,6 +197,12 @@ def visit_assign_multi(self, op: AssignMulti) -> GenAndKill[T]: def visit_set_mem(self, op: SetMem) -> GenAndKill[T]: raise NotImplementedError + def visit_inc_ref(self, op: IncRef) -> GenAndKill[T]: + return self.visit_register_op(op) + + def visit_dec_ref(self, op: DecRef) -> GenAndKill[T]: + return self.visit_register_op(op) + def visit_call(self, op: Call) -> GenAndKill[T]: return self.visit_register_op(op) diff --git a/mypyc/analysis/ircheck.py b/mypyc/analysis/ircheck.py index 238d41672924..384bcdbe5cd8 100644 --- a/mypyc/analysis/ircheck.py +++ b/mypyc/analysis/ircheck.py @@ -63,12 +63,17 @@ RPrimitive, RType, RUnion, + RVec, bytes_rprimitive, dict_rprimitive, int_rprimitive, + is_c_py_ssize_t_rprimitive, + is_fixed_width_rtype, is_float_rprimitive, is_object_rprimitive, + is_pointer_rprimitive, list_rprimitive, + pointer_rprimitive, range_rprimitive, set_rprimitive, str_rprimitive, @@ -200,7 +205,7 @@ def can_coerce_to(src: RType, dest: RType) -> bool: if src.name in disjoint_types and dest.name in disjoint_types: return src.name == dest.name return src.size == dest.size - if isinstance(src, RInstance): + if isinstance(src, (RInstance, RVec)): return is_object_rprimitive(dest) if isinstance(src, RUnion): # IR doesn't have the ability to narrow unions based on @@ -211,6 +216,29 @@ def can_coerce_to(src: RType, dest: RType) -> bool: return True +def is_valid_ptr_displacement_type(rtype: RType) -> bool: + """Check if rtype is a valid displacement type for pointer arithmetic.""" + if not (is_fixed_width_rtype(rtype) or is_c_py_ssize_t_rprimitive(rtype)): + return False + assert isinstance(rtype, RPrimitive) + return rtype.size == pointer_rprimitive.size + + +def is_pointer_arithmetic(op: IntOp) -> bool: + """Check if op is add/subtract targeting pointer_rprimitive and integer of the same size.""" + if op.op not in (IntOp.ADD, IntOp.SUB): + return False + if not is_pointer_rprimitive(op.type): + return False + left = op.lhs.type + right = op.rhs.type + if is_pointer_rprimitive(left): + return is_valid_ptr_displacement_type(right) + if is_pointer_rprimitive(right): + return is_valid_ptr_displacement_type(left) + return False + + class OpChecker(OpVisitor[None]): def __init__(self, parent_fn: FuncIR) -> None: self.parent_fn = parent_fn @@ -417,6 +445,7 @@ def visit_int_op(self, op: IntOp) -> None: op_str in ("+", "-", "*", "/", "%") or (op_str not in ("<<", ">>") and left.size != right.size) ) + and not is_pointer_arithmetic(op) ): self.fail(op, f"Operand types have incompatible signs: {left}, {right}") diff --git a/mypyc/analysis/selfleaks.py b/mypyc/analysis/selfleaks.py index b2e837565307..a3940f76d5b9 100644 --- a/mypyc/analysis/selfleaks.py +++ b/mypyc/analysis/selfleaks.py @@ -11,6 +11,7 @@ CallC, Cast, ComparisonOp, + DecRef, Extend, FloatComparisonOp, FloatNeg, @@ -19,6 +20,7 @@ GetElement, GetElementPtr, Goto, + IncRef, InitStatic, IntOp, KeepAlive, @@ -90,6 +92,12 @@ def visit_assign_multi(self, op: AssignMulti) -> GenAndKill: def visit_set_mem(self, op: SetMem) -> GenAndKill: return CLEAN + def visit_inc_ref(self, op: IncRef) -> GenAndKill: + return CLEAN + + def visit_dec_ref(self, op: DecRef) -> GenAndKill: + return CLEAN + def visit_call(self, op: Call) -> GenAndKill: fn = op.fn if fn.class_name and fn.name == "__init__": diff --git a/mypyc/ir/deps.py b/mypyc/ir/deps.py index 7c52520e274b..933ff72e40c2 100644 --- a/mypyc/ir/deps.py +++ b/mypyc/ir/deps.py @@ -55,3 +55,4 @@ def get_header(self) -> str: STRING_WRITER_EXTRA_OPS: Final = SourceDep("stringwriter_extra_ops.c") BYTEARRAY_EXTRA_OPS: Final = SourceDep("bytearray_extra_ops.c") STR_EXTRA_OPS: Final = SourceDep("str_extra_ops.c") +VECS_EXTRA_OPS: Final = SourceDep("vecs_extra_ops.c") diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index 8140660361ac..f1b5b746770f 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -39,6 +39,7 @@ class to enable the new behavior. Sometimes adding a new abstract RTuple, RType, RUnion, + RVec, RVoid, bit_rprimitive, bool_rprimitive, @@ -1690,7 +1691,7 @@ class GetElement(RegisterOp): def __init__(self, src: Value, field: str, line: int = -1) -> None: super().__init__(line) - assert isinstance(src.type, RStruct) + assert isinstance(src.type, (RStruct, RVec)) self.type = src.type.field_type(field) self.src = src self.src_type = src.type @@ -1720,7 +1721,7 @@ class GetElementPtr(RegisterOp): def __init__(self, src: Value, src_type: RType, field: str, line: int = -1) -> None: super().__init__(line) - assert not isinstance(src.type, RStruct) + assert not isinstance(src.type, (RStruct, RVec)) self.type = pointer_rprimitive self.src = src self.src_type = src_type @@ -1750,7 +1751,7 @@ class SetElement(RegisterOp): def __init__(self, src: Value, field: str, item: Value, line: int = -1) -> None: super().__init__(line) - assert isinstance(src.type, RStruct), src.type + assert isinstance(src.type, (RStruct, RVec)), src.type self.type = src.type self.src = src self.item = item diff --git a/mypyc/ir/rtypes.py b/mypyc/ir/rtypes.py index 96f6f7c676f1..82970b39010d 100644 --- a/mypyc/ir/rtypes.py +++ b/mypyc/ir/rtypes.py @@ -38,10 +38,10 @@ class to enable the new behavior. In rare cases, adding a new from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, ClassVar, Final, Generic, TypeGuard, TypeVar, final +from typing import TYPE_CHECKING, ClassVar, Final, Generic, TypeGuard, TypeVar, Union, final from mypyc.common import HAVE_IMMORTAL, IS_32_BIT_PLATFORM, PLATFORM_SIZE, JsonDict, short_name -from mypyc.ir.deps import LIBRT_STRINGS, Dependency +from mypyc.ir.deps import LIBRT_STRINGS, LIBRT_VECS, Dependency from mypyc.namegen import NameGenerator if TYPE_CHECKING: @@ -136,6 +136,8 @@ def deserialize_type(data: JsonDict | str, ctx: DeserMaps) -> RType: return RTuple.deserialize(data, ctx) elif data[".class"] == "RUnion": return RUnion.deserialize(data, ctx) + elif data[".class"] == "RVec": + return RVec.deserialize(data, ctx) raise NotImplementedError("unexpected .class {}".format(data[".class"])) @@ -150,6 +152,10 @@ def visit_rprimitive(self, typ: RPrimitive, /) -> T: def visit_rinstance(self, typ: RInstance, /) -> T: raise NotImplementedError + @abstractmethod + def visit_rvec(self, typ: RVec, /) -> T: + raise NotImplementedError + @abstractmethod def visit_runion(self, typ: RUnion, /) -> T: raise NotImplementedError @@ -171,6 +177,10 @@ def visit_rvoid(self, typ: RVoid, /) -> T: raise NotImplementedError +# These types are implemented as C structs. +RStructLike = Union["RStruct", "RVec"] + + @final class RVoid(RType): """The void type (no value). @@ -312,6 +322,12 @@ def __hash__(self) -> int: "object_ptr", is_unboxed=False, is_refcounted=False, ctype="PyObject **" ) +# Similar to object_primitive, but does not use automatic reference +# counting. Useful for temporaries. +object_non_refcounted_rprimitive: Final = RPrimitive( + "builtins.object_nrc", is_unboxed=False, is_refcounted=False +) + # Arbitrary-precision integer (corresponds to Python 'int'). Small # enough values are stored unboxed, while large integers are # represented as a tagged pointer to a Python 'int' PyObject. The @@ -542,6 +558,10 @@ def is_tagged(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int_rprimitive or rtype is short_int_rprimitive +def is_any_int(rtype: RType) -> bool: + return is_tagged(rtype) or is_int32_rprimitive(rtype) or is_int64_rprimitive(rtype) + + def is_int_rprimitive(rtype: RType) -> TypeGuard[RPrimitive]: return rtype is int_rprimitive @@ -680,6 +700,12 @@ class TupleNameVisitor(RTypeVisitor[str]): def visit_rinstance(self, t: RInstance) -> str: return "O" + def visit_rvec(self, t: RVec) -> str: + if isinstance(t.item_type, RVec): + # All nested vecs share a representation + return "Vv" + return "V" + t.item_type.accept(self) + def visit_runion(self, t: RUnion) -> str: return "O" @@ -706,6 +732,8 @@ def visit_rtuple(self, t: RTuple) -> str: return "T{}{}".format(len(parts), "".join(parts)) def visit_rstruct(self, t: RStruct) -> str: + if t.name == "VecNestedBufItem": + return "Vi" assert False, "RStruct not supported in tuple" def visit_rarray(self, t: RArray) -> str: @@ -980,6 +1008,105 @@ def serialize(self) -> str: return self.name +@final +class RVec(RType): + """librt.vecs.vec[T]""" + + is_unboxed = True + + def __init__(self, item_type: RType) -> None: + self.name = "vec[%s]" % item_type + self.item_type = item_type + self.names = ["len", "buf"] + self.dependencies = (LIBRT_VECS,) + if isinstance(item_type, RUnion): + non_opt = optional_value_type(item_type) + else: + non_opt = item_type + if item_type in vec_buf_types: + self._ctype = vec_c_types[item_type] + self.buf_type = vec_buf_types[item_type] + self.types = [c_pyssize_t_rprimitive, self.buf_type] + elif isinstance(non_opt, RVec): + self._ctype = "VecNested" + self.types = [c_pyssize_t_rprimitive, VecTBufObject] + self.buf_type = VecNestedBufObject + else: + self._ctype = "VecT" + self.types = [c_pyssize_t_rprimitive, VecTBufObject] + self.buf_type = VecTBufObject + + @property + def may_be_immortal(self) -> bool: + return False + + def unwrap_item_type(self) -> RPrimitive | RInstance: + """Return the non-optional value (non-vec) item type in a potentially nested vec.""" + item_type = self.item_type + while True: + if isinstance(item_type, RUnion): + value_type = optional_value_type(item_type) + assert value_type is not None + item_type = value_type + elif isinstance(item_type, RVec): + item_type = item_type.item_type + elif isinstance(item_type, (RPrimitive, RInstance)): + return item_type + else: + assert False, f"unexpected item type: {self.item_type}" + + def is_optional(self) -> bool: + item_type = self.item_type + if isinstance(item_type, RUnion): + return True + elif isinstance(item_type, RVec): + return item_type.is_optional() + return False + + def depth(self) -> int: + item_type = self.item_type + if isinstance(item_type, RUnion): + value_type = optional_value_type(item_type) + assert value_type is not None + item_type = value_type + if isinstance(item_type, RVec): + return 1 + item_type.depth() + return 0 + + def field_type(self, name: str) -> RType: + if name == "len": + return c_pyssize_t_rprimitive + elif name == "buf": + return object_rprimitive + assert False, f"RVec has no field '{name}'" + + def accept(self, visitor: RTypeVisitor[T]) -> T: + return visitor.visit_rvec(self) + + def __str__(self) -> str: + if self.is_optional() and self.depth() == 0: + type_str = f"{self.unwrap_item_type()} | None" + else: + type_str = str(self.item_type) + return f"vec[{type_str}]" + + def __repr__(self) -> str: + return "" % self.item_type + + def __eq__(self, other: object) -> bool: + return isinstance(other, RVec) and other.item_type == self.item_type + + def __hash__(self) -> int: + return hash(self.item_type) ^ 1 + + def serialize(self) -> JsonDict: + return {".class": "RVec", "item_type": self.item_type.serialize()} + + @classmethod + def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> RVec: + return RVec(deserialize_type(data["item_type"], ctx)) + + @final class RUnion(RType): """union[x, ..., y]""" @@ -1175,3 +1302,143 @@ def check_native_int_range(rtype: RPrimitive, n: int) -> bool: else: limit = 1 << (rtype.size * 8 - 1) return -limit <= n < limit + + +# Buffers for vec item types that have a packed representation +# +# Note that the 'items' fields are variable-length arrays, and mypyc IR isn't +# able to represent these, so the field type is omitted for now. + +VecI64BufObject = RStruct( + name="VecI64BufObject", + names=["ob_base", "len", "items"], + types=[PyVarObject, int64_rprimitive], +) + +VecI32BufObject = RStruct( + name="VecI32BufObject", + names=["ob_base", "len", "items"], + types=[PyVarObject, int64_rprimitive], +) + +VecI16BufObject = RStruct( + name="VecI16BufObject", + names=["ob_base", "len", "items"], + types=[PyVarObject, int64_rprimitive], +) + +VecU8BufObject = RStruct( + name="VecU8BufObject", names=["ob_base", "len", "items"], types=[PyVarObject, int64_rprimitive] +) + +VecFloatBufObject = RStruct( + name="VecFloatBufObject", + names=["ob_base", "len", "items"], + types=[PyVarObject, int64_rprimitive], +) + +VecBoolBufObject = RStruct( + name="VecBoolBufObject", + names=["ob_base", "len", "items"], + types=[PyVarObject, int64_rprimitive], +) + + +# Struct type for vec[i64] (in most cases use RVec instead). +VecI64 = RStruct( + name="VecI64", names=["len", "buf"], types=[c_pyssize_t_rprimitive, object_rprimitive] +) + + +# Buffer for vec[t] +VecTBufObject = RStruct( + name="VecTBufObject", + names=["ob_base", "item_type", "items"], + types=[PyVarObject, c_pyssize_t_rprimitive, object_rprimitive], +) + +# Struct type for vec[t] (in most cases use RVec instead). +VecT = RStruct( + name="VecT", names=["len", "buf"], types=[c_pyssize_t_rprimitive, object_rprimitive] +) + +VecNestedBufItem = RStruct( + name="VecNestedBufItem", + names=["len", "buf"], + types=[c_pyssize_t_rprimitive, object_non_refcounted_rprimitive], +) + +# Buffer for vec[vec[t]] +VecNestedBufObject = RStruct( + name="VecNestedBufObject", + names=["ob_base", "item_type", "depth", "optionals", "items"], + types=[ + PyVarObject, + c_pyssize_t_rprimitive, + int32_rprimitive, + int32_rprimitive, + VecNestedBufItem, + ], +) + +# Struct type for vec[vec[...]] (in most cases use RVec instead). +VecNested = RStruct( + name="VecNested", names=["len", "buf"], types=[c_pyssize_t_rprimitive, object_rprimitive] +) + +VecNestedBufObject_rprimitive = RPrimitive( + "VecNestedBufObject_ptr", is_unboxed=False, is_refcounted=True, ctype="VecNestedBufObject *" +) + +VecNestedPopResult = RStruct( + name="VecNestedPopResult", names=["vec", "item"], types=[VecNested, VecNestedBufItem] +) + + +vec_buf_types: Final[dict[RType, RStruct]] = { + int64_rprimitive: VecI64BufObject, + int32_rprimitive: VecI32BufObject, + int16_rprimitive: VecI16BufObject, + uint8_rprimitive: VecU8BufObject, + float_rprimitive: VecFloatBufObject, + bool_rprimitive: VecBoolBufObject, +} + +vec_c_types: Final[dict[RType, str]] = { + int64_rprimitive: "VecI64", + int32_rprimitive: "VecI32", + int16_rprimitive: "VecI16", + uint8_rprimitive: "VecU8", + float_rprimitive: "VecFloat", + bool_rprimitive: "VecBool", +} + +vec_api_fields: Final[dict[RType, str]] = { + int64_rprimitive: "i64", + int32_rprimitive: "i32", + int16_rprimitive: "i16", + uint8_rprimitive: "u8", + float_rprimitive: "float_", + bool_rprimitive: "bool_", +} + +vec_api_by_item_type: Final[dict[RType, str]] = { + int64_rprimitive: "VecI64Api", + int32_rprimitive: "VecI32Api", + int16_rprimitive: "VecI16Api", + uint8_rprimitive: "VecU8Api", + float_rprimitive: "VecFloatApi", + bool_rprimitive: "VecBoolApi", +} + +# These are special item type constants used in nested vecs to represent +# item types with specialized representations. These must match definitions +# in the vecs module (see VEC_ITEM_TYPE_I64 etc.). +vec_item_type_tags: Final[dict[RType, int]] = { + int64_rprimitive: 2, + int32_rprimitive: 6, + int16_rprimitive: 10, + uint8_rprimitive: 14, + float_rprimitive: 18, + bool_rprimitive: 22, +} diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 164765dd8db4..e76e6a30cd4d 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -92,6 +92,7 @@ RTuple, RType, RUnion, + RVec, bitmap_rprimitive, bool_rprimitive, bytes_rprimitive, @@ -128,6 +129,7 @@ AssignmentTargetTuple, ) from mypyc.irbuild.util import bytes_from_str, is_constant +from mypyc.irbuild.vec import vec_set_item from mypyc.options import CompilerOptions from mypyc.primitives.dict_ops import dict_get_item_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op, next_op, py_setattr_op @@ -420,8 +422,8 @@ def int_op(self, type: RType, lhs: Value, rhs: Value, op: int, line: int) -> Val def compare_tuples(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: return self.builder.compare_tuples(lhs, rhs, op, line) - def builtin_len(self, val: Value, line: int) -> Value: - return self.builder.builtin_len(val, line) + def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Value: + return self.builder.builtin_len(val, line, use_pyssize_t) def new_tuple(self, items: list[Value], line: int) -> Value: return self.builder.new_tuple(items, line) @@ -769,10 +771,13 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i boxed_reg = self.builder.box(rvalue_reg) self.primitive_op(py_setattr_op, [target.obj, key, boxed_reg], line) elif isinstance(target, AssignmentTargetIndex): - target_reg2 = self.gen_method_call( - target.base, "__setitem__", [target.index, rvalue_reg], None, line - ) - assert target_reg2 is not None, target.base.type + if isinstance(target.base.type, RVec): + vec_set_item(self.builder, target.base, target.index, rvalue_reg, line) + else: + target_reg2 = self.gen_method_call( + target.base, "__setitem__", [target.index, rvalue_reg], None, line + ) + assert target_reg2 is not None, target.base.type elif isinstance(target, AssignmentTargetTuple): if isinstance(rvalue_reg.type, RTuple) and target.star_idx is None: rtypes = rvalue_reg.type.types diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index e99542138ed7..1e7ece6eeacf 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -78,9 +78,12 @@ from mypyc.ir.rtypes import ( RInstance, RTuple, + RVec, bool_rprimitive, int_rprimitive, + is_any_int, is_fixed_width_rtype, + is_int64_rprimitive, is_int_rprimitive, is_list_rprimitive, is_none_rprimitive, @@ -96,6 +99,7 @@ raise_error_if_contains_unreachable_names, translate_list_comprehension, translate_set_comprehension, + translate_vec_comprehension, ) from mypyc.irbuild.format_str_tokenizer import ( convert_format_expr_to_bytes, @@ -111,6 +115,13 @@ translate_object_new, translate_object_setattr, ) +from mypyc.irbuild.vec import ( + vec_append, + vec_create, + vec_create_from_values, + vec_create_initialized, + vec_slice, +) from mypyc.primitives.bytes_ops import bytes_slice_op from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, exact_dict_set_item_op from mypyc.primitives.generic_ops import iter_op, name_op @@ -334,7 +345,19 @@ def transform_call_expr(builder: IRBuilder, expr: CallExpr) -> Value: return builder.accept(expr.args[0]) if isinstance(callee, IndexExpr) and isinstance(callee.analyzed, TypeApplication): - callee = callee.analyzed.expr # Unwrap type application + analyzed = callee.analyzed + if ( + isinstance(analyzed.expr, RefExpr) + and analyzed.expr.fullname == "librt.vecs.vec" + and len(analyzed.types) == 1 + ): + item_type = builder.type_to_rtype(analyzed.types[0]) + vec_type = RVec(item_type) + if len(expr.args) == 0: + return vec_create(builder.builder, vec_type, 0, expr.line) + elif len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]: + return translate_vec_create_from_iterable(builder, vec_type, expr.args[0]) + callee = analyzed.expr # Unwrap type application if isinstance(callee, MemberExpr): if isinstance(callee.expr, RefExpr) and isinstance(callee.expr.node, MypyFile): @@ -527,6 +550,65 @@ def translate_super_method_call(builder: IRBuilder, expr: CallExpr, callee: Supe return builder.builder.call(decl, arg_values, arg_kinds, arg_names, expr.line) +def translate_vec_create_from_iterable( + builder: IRBuilder, vec_type: RVec, arg: Expression +) -> Value: + line = arg.line + item_type = vec_type.item_type + if isinstance(arg, OpExpr) and arg.op == "*": + if isinstance(arg.left, ListExpr): + lst = arg.left + other = arg.right + elif isinstance(arg.right, ListExpr): + lst = arg.right + other = arg.left + else: + assert False + assert len(lst.items) == 1 + other_type = builder.node_type(other) + # TODO: is_any_int(...) + if is_int64_rprimitive(other_type) or is_int_rprimitive(other_type): + length = builder.accept(other) + init = builder.accept(lst.items[0]) + return vec_create_initialized(builder.builder, vec_type, length, init, line) + assert False, other_type + if isinstance(arg, ListExpr): + items = [] + for item in arg.items: + value = builder.accept(item) + items.append(builder.coerce(value, item_type, line)) + return vec_create_from_values(builder.builder, vec_type, items, line) + if isinstance(arg, ListComprehension): + return translate_vec_comprehension(builder, vec_type, arg.generator) + return vec_from_iterable(builder, vec_type, arg, line) + + +def vec_from_iterable( + builder: IRBuilder, vec_type: RVec, iterable: Expression, line: int +) -> Value: + """Construct a vec from an arbitrary iterable.""" + # Translate it as a vec comprehension vec[t]([ for in + # iterable]). This way we can use various special casing supported + # by for loops and comprehensions. + vec = Register(vec_type) + builder.assign(vec, vec_create(builder.builder, vec_type, 0, line), line) + name = f"___tmp_{line}" + var = Var(name) + reg = builder.add_local(var, vec_type.item_type) + index = NameExpr(name) + index.kind = LDEF + index.node = var + loop_params: list[tuple[Expression, Expression, list[Expression], bool]] = [ + (index, iterable, [], False) + ] + + def gen_inner_stmts() -> None: + builder.assign(vec, vec_append(builder.builder, vec, reg, line), line) + + comprehension_helper(builder, loop_params, gen_inner_stmts, line) + return vec + + def translate_cast_expr(builder: IRBuilder, expr: CastExpr) -> Value: src = builder.accept(expr.expr) target_type = builder.type_to_rtype(expr.type) @@ -596,8 +678,8 @@ def try_optimize_int_floor_divide(builder: IRBuilder, expr: OpExpr) -> OpExpr: def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: index = expr.index base_type = builder.node_type(expr.base) - is_list = is_list_rprimitive(base_type) - can_borrow_base = is_list and is_borrow_friendly_expr(builder, index) + can_borrow = is_list_rprimitive(base_type) or isinstance(base_type, RVec) + can_borrow_base = can_borrow and is_borrow_friendly_expr(builder, index) # Check for dunder specialization for non-slice indexing if not isinstance(index, SliceExpr): @@ -619,9 +701,9 @@ def transform_index_expr(builder: IRBuilder, expr: IndexExpr) -> Value: if value: return value - index_reg = builder.accept(expr.index, can_borrow=is_list) - return builder.gen_method_call( - base, "__getitem__", [index_reg], builder.node_type(expr), expr.line + index_reg = builder.accept(expr.index, can_borrow=can_borrow) + return builder.builder.get_item( + base, index_reg, builder.node_type(expr), expr.line, can_borrow=builder.can_borrow ) @@ -657,7 +739,7 @@ def try_gen_slice_op(builder: IRBuilder, base: Value, index: SliceExpr) -> Value end_type = int_rprimitive # Both begin and end index must be int (or missing). - if is_int_rprimitive(begin_type) and is_int_rprimitive(end_type): + if is_any_int(begin_type) and is_any_int(end_type): if index.begin_index: begin = builder.accept(index.begin_index) else: @@ -668,6 +750,8 @@ def try_gen_slice_op(builder: IRBuilder, base: Value, index: SliceExpr) -> Value # Replace missing end index with the largest short integer # (a sequence can't be longer). end = builder.load_int(MAX_SHORT_INT) + if isinstance(base.type, RVec): + return vec_slice(builder.builder, base, begin, end, index.line) candidates = [list_slice_op, tuple_slice_op, str_slice_op, bytes_slice_op] return builder.builder.matching_call_c(candidates, [base, begin, end], index.line) @@ -882,6 +966,14 @@ def translate_is_none(builder: IRBuilder, expr: Expression, negated: bool) -> Va def transform_basic_comparison( builder: IRBuilder, op: str, left: Value, right: Value, line: int ) -> Value: + """Transform single comparison. + + 'op' must be one of these: + + '==', '!=', '<', '<=', '>', '>=' + 'in', 'not in' + 'is', 'is not' + """ if is_fixed_width_rtype(left.type) and op in ComparisonOp.signed_ops: if right.type == left.type: if left.type.is_signed: diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 5c7589f99dfc..949ae458a9ce 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -49,6 +49,7 @@ RInstance, RTuple, RType, + RVec, bool_rprimitive, c_pyssize_t_rprimitive, int_rprimitive, @@ -69,6 +70,7 @@ from mypyc.irbuild.constant_fold import constant_fold_expr from mypyc.irbuild.prepare import GENERATOR_HELPER_NAME from mypyc.irbuild.targets import AssignmentTarget, AssignmentTargetTuple +from mypyc.irbuild.vec import vec_append, vec_create, vec_get_item_unsafe, vec_init_item_unsafe from mypyc.primitives.dict_ops import ( dict_check_size_op, dict_item_iter_op, @@ -172,7 +174,10 @@ def for_loop_helper_with_index( body_insts: a function that generates the body of the loop. It needs a index as parameter. """ - assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type) + assert is_sequence_rprimitive(expr_reg.type) or isinstance(expr_reg.type, RVec), ( + expr_reg, + expr_reg.type, + ) target_type = builder.get_sequence_type(expr) body_block = BasicBlock() @@ -211,7 +216,7 @@ def sequence_from_generator_preallocate_helper( builder: IRBuilder, gen: GeneratorExpr, empty_op_llbuilder: Callable[[Value, int], Value], - set_item_op: CFunctionDescription, + set_item_op: Callable[[Value, Value, Value, int], None], ) -> Value | None: """Generate a new tuple or list from a simple generator expression. @@ -239,7 +244,7 @@ def sequence_from_generator_preallocate_helper( line = gen.line sequence_expr = gen.sequences[0] rtype = builder.node_type(sequence_expr) - if not (is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple)): + if not (is_sequence_rprimitive(rtype) or isinstance(rtype, (RTuple, RVec))): return None if isinstance(rtype, RTuple): @@ -284,7 +289,7 @@ def sequence_from_generator_preallocate_helper( def set_item(item_index: Value) -> None: e = builder.accept(gen.left_expr) - builder.call_c(set_item_op, [target_op, item_index, e], line) + set_item_op(target_op, item_index, e, line) for_loop_helper_with_index( builder, gen.indices[0], sequence_expr, sequence, set_item, line, length @@ -298,12 +303,15 @@ def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Valu if raise_error_if_contains_unreachable_names(builder, gen): return builder.none() + def set_item(x: Value, y: Value, z: Value, line: int) -> None: + builder.call_c(new_list_set_item_op, [x, y, z], line) + # Try simplest list comprehension, otherwise fall back to general one val = sequence_from_generator_preallocate_helper( builder, gen, empty_op_llbuilder=builder.builder.new_list_op_with_length, - set_item_op=new_list_set_item_op, + set_item_op=set_item, ) if val is not None: return val @@ -355,6 +363,34 @@ def gen_inner_stmts() -> None: return builder.read(set_ops, gen.line) +def translate_vec_comprehension(builder: IRBuilder, vec_type: RVec, gen: GeneratorExpr) -> Value: + def set_item(x: Value, y: Value, z: Value, line: int) -> None: + vec_init_item_unsafe(builder.builder, x, y, z, line) + + # Try simplest comprehension, otherwise fall back to general one + val = sequence_from_generator_preallocate_helper( + builder, + gen, + empty_op_llbuilder=lambda length, line: vec_create( + builder.builder, vec_type, length, line + ), + set_item_op=set_item, + ) + if val is not None: + return val + + vec = Register(vec_type) + builder.assign(vec, vec_create(builder.builder, vec_type, 0, gen.line), gen.line) + loop_params = list(zip(gen.indices, gen.sequences, gen.condlists, gen.is_async)) + + def gen_inner_stmts() -> None: + e = builder.accept(gen.left_expr) + builder.assign(vec, vec_append(builder.builder, vec, e, gen.line), gen.line) + + comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line) + return vec + + def comprehension_helper( builder: IRBuilder, loop_params: list[tuple[Lvalue, Expression, list[Expression], bool]], @@ -455,8 +491,8 @@ def make_for_loop_generator( return async_obj rtyp = builder.node_type(expr) - if is_sequence_rprimitive(rtyp): - # Special case "for x in ". + if is_sequence_rprimitive(rtyp) or isinstance(rtyp, RVec): + # Special case "for x in " for concrete sequence types. expr_reg = builder.accept(expr) target_type = builder.get_sequence_type(expr) @@ -831,6 +867,8 @@ def unsafe_index(builder: IRBuilder, target: Value, index: Value, line: int) -> return builder.call_c(tuple_get_item_unsafe_op, [target, index], line) elif is_str_rprimitive(target.type): return builder.call_c(str_get_item_unsafe_op, [target, index], line) + elif isinstance(target.type, RVec): + return vec_get_item_unsafe(builder.builder, target, index, line) else: return builder.gen_method_call(target, "__getitem__", [index], None, line) @@ -846,7 +884,10 @@ class ForSequence(ForGenerator): def init( self, expr_reg: Value, target_type: RType, reverse: bool, length: Value | None = None ) -> None: - assert is_sequence_rprimitive(expr_reg.type), (expr_reg, expr_reg.type) + assert is_sequence_rprimitive(expr_reg.type) or isinstance(expr_reg.type, RVec), ( + expr_reg, + expr_reg.type, + ) builder = self.builder # Record a Value indicating the length of the sequence, if known at compile time. self.length = length @@ -1297,7 +1338,7 @@ def get_expr_length_value( builder: IRBuilder, expr: Expression, expr_reg: Value, line: int, use_pyssize_t: bool ) -> Value: rtype = builder.node_type(expr) - assert is_sequence_rprimitive(rtype) or isinstance(rtype, RTuple), rtype + assert is_sequence_rprimitive(rtype) or isinstance(rtype, (RTuple, RVec)), rtype length = get_expr_length(builder, expr) if length is None: # We cannot compute the length at compile time, so we will fetch it. diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 99d62adca970..acc8acbe9130 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -49,6 +49,7 @@ FloatNeg, FloatOp, GetAttr, + GetElement, GetElementPtr, Goto, Integer, @@ -87,6 +88,7 @@ RTuple, RType, RUnion, + RVec, bit_rprimitive, bitmap_rprimitive, bool_rprimitive, @@ -97,6 +99,7 @@ c_size_t_rprimitive, check_native_int_range, float_rprimitive, + int64_rprimitive, int_rprimitive, is_bool_or_bit_rprimitive, is_bytes_rprimitive, @@ -128,6 +131,7 @@ str_rprimitive, ) from mypyc.irbuild.util import concrete_arg_kind +from mypyc.irbuild.vec import vec_contains, vec_get_item, vec_len_native from mypyc.options import CompilerOptions from mypyc.primitives.bytes_ops import bytes_compare from mypyc.primitives.dict_ops import ( @@ -316,6 +320,9 @@ def load_mem(self, ptr: Value, value_type: RType, *, borrow: bool = False) -> Va def set_mem(self, ptr: Value, value_type: RType, value: Value) -> None: self.add(SetMem(value_type, ptr, value, line=-1)) + def get_element(self, reg: Value, field: str) -> Value: + return self.add(GetElement(reg, field)) + def load_address(self, name: str, rtype: RType) -> Value: return self.add(LoadAddress(rtype, name)) @@ -1653,6 +1660,9 @@ def binary_op(self, lreg: Value, rreg: Value, op: str, line: int) -> Value: if dunder_op: return dunder_op + if isinstance(rtype, RVec) and op == "in": + return vec_contains(self, rreg, lreg, line) + primitive_ops_candidates = binary_ops.get(op, []) target = self.matching_primitive_op(primitive_ops_candidates, [lreg, rreg], line) assert target, "Unsupported binary operation: %s" % op @@ -1977,6 +1987,26 @@ def unary_op(self, value: Value, op: str, line: int) -> Value: return self.unary_invert(value, line) raise RuntimeError("Unsupported unary operation: %s" % op) + def get_item( + self, + base: Value, + item: Value, + result_type: RType | None, + line: int, + *, + can_borrow: bool = False, + ) -> Value: + """Generate base[item].""" + if isinstance(base.type, RVec): + if is_int_rprimitive(item.type) or is_short_int_rprimitive(item.type): + item = self.coerce(item, int64_rprimitive, line) + if is_int64_rprimitive(item.type): + return vec_get_item(self, base, item, line, can_borrow=can_borrow) + # TODO: Move special casing for RTuple here from transform_index_expr + return self.gen_method_call( + base, "__getitem__", [item], result_type, line, can_borrow=can_borrow + ) + def make_dict(self, key_value_pairs: Sequence[DictEntry], line: int) -> Value: result: Value | None = None keys: list[Value] = [] @@ -2655,6 +2685,13 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val self.activate_block(ok) return length + elif isinstance(typ, RVec): + len_value = vec_len_native(self, val) + if use_pyssize_t: + return len_value + count = Integer(1, c_pyssize_t_rprimitive, line) + return self.int_op(short_int_rprimitive, len_value, count, IntOp.LEFT_SHIFT, line) + op = self.matching_primitive_op(function_ops["builtins.len"], [val], line) if op is not None: return op @@ -2776,12 +2813,36 @@ def translate_special_method_call( Return None if no translation found; otherwise return the target register. """ + low_level_op = self._translate_special_low_level_method_call( + base_reg, name, args, result_type, line, can_borrow + ) + if low_level_op is not None: + return low_level_op primitive_ops_candidates = method_call_ops.get(name, []) primitive_op = self.matching_primitive_op( primitive_ops_candidates, [base_reg] + args, line, result_type, can_borrow=can_borrow ) return primitive_op + def _translate_special_low_level_method_call( + self, + base_reg: Value, + name: str, + args: list[Value], + result_type: RType | None, + line: int, + can_borrow: bool = False, + ) -> Value | None: + if name == "__getitem__" and len(args) == 1: + arg = args[0] + if isinstance(base_reg.type, RVec) and ( + is_int64_rprimitive(arg.type) or is_tagged(arg.type) + ): + if is_tagged(arg.type): + arg = self.coerce(arg, int64_rprimitive, line) + return vec_get_item(self, base_reg, arg, line) + return None + def translate_eq_cmp(self, lreg: Value, rreg: Value, expr_op: str, line: int) -> Value | None: """Add an equality comparison operation. diff --git a/mypyc/irbuild/mapper.py b/mypyc/irbuild/mapper.py index 550dc6e42c9e..523c718c9b86 100644 --- a/mypyc/irbuild/mapper.py +++ b/mypyc/irbuild/mapper.py @@ -30,6 +30,7 @@ RTuple, RType, RUnion, + RVec, bool_rprimitive, bytearray_rprimitive, bytes_rprimitive, @@ -123,6 +124,8 @@ def type_to_rtype(self, typ: Type | None) -> RType: return int16_rprimitive elif typ.type.fullname == "mypy_extensions.u8": return uint8_rprimitive + elif typ.type.fullname == "librt.vecs.vec": + return RVec(self.type_to_rtype(typ.args[0])) elif typ.type.fullname in KNOWN_NATIVE_TYPES: return KNOWN_NATIVE_TYPES[typ.type.fullname] else: diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index ca7e5a004f3e..cf460b94e082 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -54,6 +54,7 @@ RPrimitive, RTuple, RType, + RVec, bool_rprimitive, bytes_rprimitive, bytes_writer_rprimitive, @@ -98,6 +99,7 @@ join_formatted_strings, tokenizer_format_call, ) +from mypyc.irbuild.vec import vec_append, vec_pop, vec_remove from mypyc.primitives.bytearray_ops import isinstance_bytearray from mypyc.primitives.bytes_ops import ( bytes_adjust_index_op, @@ -129,6 +131,7 @@ string_writer_get_item_unsafe_op, string_writer_range_check_op, ) +from mypyc.primitives.librt_vecs_ops import isinstance_vec from mypyc.primitives.list_ops import isinstance_list, new_list_set_item_op from mypyc.primitives.misc_ops import isinstance_bool from mypyc.primitives.set_ops import isinstance_frozenset, isinstance_set @@ -313,11 +316,17 @@ def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value arg = expr.args[0] expr_rtype = builder.node_type(arg) # NOTE (?) I'm not sure if my handling of can_borrow is correct here - obj = builder.accept(arg, can_borrow=is_list_rprimitive(expr_rtype)) + obj = builder.accept( + arg, can_borrow=is_list_rprimitive(expr_rtype) or isinstance(expr_rtype, RVec) + ) if is_sequence_rprimitive(expr_rtype) or isinstance(expr_rtype, RTuple): return get_expr_length_value(builder, arg, obj, expr.line, use_pyssize_t=False) else: - return builder.builtin_len(obj, expr.line) + # TODO: Decide type of result based on context somehow? + if isinstance(obj.type, RVec): + return builder.builtin_len(obj, expr.line, use_pyssize_t=True) + else: + return builder.builtin_len(obj, expr.line) return None @@ -368,11 +377,15 @@ def translate_list_from_generator_call( and expr.arg_kinds[0] == ARG_POS and isinstance(expr.args[0], GeneratorExpr) ): + + def set_item(x: Value, y: Value, z: Value, line: int) -> None: + builder.call_c(new_list_set_item_op, [x, y, z], line) + return sequence_from_generator_preallocate_helper( builder, expr.args[0], empty_op_llbuilder=builder.builder.new_list_op_with_length, - set_item_op=new_list_set_item_op, + set_item_op=set_item, ) return None @@ -393,11 +406,15 @@ def translate_tuple_from_generator_call( and expr.arg_kinds[0] == ARG_POS and isinstance(expr.args[0], GeneratorExpr) ): + + def set_item(x: Value, y: Value, z: Value, line: int) -> None: + builder.call_c(new_tuple_set_item_op, [x, y, z], line) + return sequence_from_generator_preallocate_helper( builder, expr.args[0], empty_op_llbuilder=builder.builder.new_tuple_with_length, - set_item_op=new_tuple_set_item_op, + set_item_op=set_item, ) return None @@ -667,6 +684,7 @@ def gen_inner_stmts() -> None: "builtins.set": isinstance_set, "builtins.str": isinstance_str, "builtins.tuple": isinstance_tuple, + "librt.vecs.vec": isinstance_vec, } @@ -1487,3 +1505,44 @@ def translate_bytes_get_item( bytes_range_check_op, bytes_get_item_unsafe_op, ) + + +@specialize_function("librt.vecs.append") +def translate_vec_append(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]: + vec_arg = expr.args[0] + item_arg = expr.args[1] + vec_type = builder.node_type(vec_arg) + if isinstance(vec_type, RVec): + vec_value = builder.accept(vec_arg) + arg_value = builder.accept(item_arg) + return vec_append(builder.builder, vec_value, arg_value, item_arg.line) + return None + + +@specialize_function("librt.vecs.remove") +def translate_vec_remove(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if len(expr.args) == 2 and expr.arg_kinds == [ARG_POS, ARG_POS]: + vec_arg = expr.args[0] + item_arg = expr.args[1] + vec_type = builder.node_type(vec_arg) + if isinstance(vec_type, RVec): + vec_value = builder.accept(vec_arg) + arg_value = builder.accept(item_arg) + return vec_remove(builder.builder, vec_value, arg_value, item_arg.line) + return None + + +@specialize_function("librt.vecs.pop") +def translate_vec_pop(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Value | None: + if 1 <= len(expr.args) <= 2 and all(kind == ARG_POS for kind in expr.arg_kinds): + vec_arg = expr.args[0] + vec_type = builder.node_type(vec_arg) + if isinstance(vec_type, RVec): + vec_value = builder.accept(vec_arg) + if len(expr.args) == 2: + index_value = builder.accept(expr.args[1]) + else: + index_value = Integer(-1, int64_rprimitive) + return vec_pop(builder.builder, vec_value, index_value, vec_arg.line) + return None diff --git a/mypyc/irbuild/vec.py b/mypyc/irbuild/vec.py new file mode 100644 index 000000000000..6177b492da9c --- /dev/null +++ b/mypyc/irbuild/vec.py @@ -0,0 +1,537 @@ +"""Generate IR for librt.vecs.vec operations""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Final, cast + +from mypyc.common import IS_32_BIT_PLATFORM, PLATFORM_SIZE +from mypyc.ir.ops import ( + ERR_MAGIC, + ERR_NEVER, + Assign, + BasicBlock, + Branch, + CallC, + ComparisonOp, + DecRef, + GetElement, + GetElementPtr, + Integer, + IntOp, + RaiseStandardError, + Register, + SetElement, + TupleGet, + TupleSet, + Unborrow, + Undef, + Unreachable, + Value, +) +from mypyc.ir.rtypes import ( + RInstance, + RPrimitive, + RTuple, + RType, + RUnion, + RVec, + VecNestedBufItem, + bool_rprimitive, + c_pyssize_t_rprimitive, + c_size_t_rprimitive, + int32_rprimitive, + int64_rprimitive, + is_c_py_ssize_t_rprimitive, + is_int32_rprimitive, + is_int64_rprimitive, + is_int_rprimitive, + is_short_int_rprimitive, + object_rprimitive, + optional_value_type, + pointer_rprimitive, + vec_api_by_item_type, + vec_item_type_tags, +) +from mypyc.primitives.registry import builtin_names + +if TYPE_CHECKING: + from mypyc.irbuild.ll_builder import LowLevelIRBuilder + + +def as_platform_int(builder: LowLevelIRBuilder, v: Value, line: int) -> Value: + rtype = v.type + if is_c_py_ssize_t_rprimitive(rtype): + return v + if isinstance(v, Integer): + if is_short_int_rprimitive(rtype) or is_int_rprimitive(rtype): + return Integer(v.value // 2, c_pyssize_t_rprimitive) + return Integer(v.value, c_pyssize_t_rprimitive) + if isinstance(rtype, RPrimitive): + if PLATFORM_SIZE == 8 and is_int64_rprimitive(rtype): + return v + if PLATFORM_SIZE == 4 and is_int32_rprimitive(rtype): + return v + return builder.coerce(v, c_pyssize_t_rprimitive, line) + + +def vec_create(builder: LowLevelIRBuilder, vtype: RVec, length: int | Value, line: int) -> Value: + if isinstance(length, int): + length = Integer(length, c_pyssize_t_rprimitive) + length = as_platform_int(builder, length, line) + + item_type = vtype.item_type + api_name = vec_api_by_item_type.get(item_type) + if api_name is not None: + call = CallC( + f"{api_name}.alloc", + [length, length], + vtype, + False, + False, + error_kind=ERR_MAGIC, + line=line, + ) + return builder.add(call) + + typeobj, optional, depth = vec_item_type_info(builder, item_type, line) + if typeobj is not None: + typeval: Value + if isinstance(typeobj, Integer): + typeval = typeobj + else: + # Create an integer which will hold the type object * as an integral value. + # Assign implicitly coerces between pointer/integer types. + typeval = Register(pointer_rprimitive) + builder.add(Assign(typeval, typeobj)) + if optional: + typeval = builder.add( + IntOp(pointer_rprimitive, typeval, Integer(1, pointer_rprimitive), IntOp.OR) + ) + if depth == 0: + call = CallC( + "VecTApi.alloc", + [length, length, typeval], + vtype, + False, + False, + error_kind=ERR_MAGIC, + line=line, + ) + return builder.add(call) + else: + call = CallC( + "VecNestedApi.alloc", + [length, length, typeval, Integer(depth, int32_rprimitive)], + vtype, + False, + False, + error_kind=ERR_MAGIC, + line=line, + ) + return builder.add(call) + + assert False, "unsupported: %s" % vtype + + +def vec_create_initialized( + builder: LowLevelIRBuilder, vtype: RVec, length: int | Value, init: Value, line: int +) -> Value: + """Create vec with items initialized to the given value.""" + if isinstance(length, int): + length = Integer(length, c_pyssize_t_rprimitive) + length = as_platform_int(builder, length, line) + + item_type = vtype.item_type + init = builder.coerce(init, item_type, line) + vec = vec_create(builder, vtype, length, line) + + items_start = vec_items(builder, vec) + step = step_size(item_type) + items_end = builder.int_add(items_start, builder.int_mul(length, step)) + + for_loop = builder.begin_for( + items_start, items_end, Integer(step, c_pyssize_t_rprimitive), signed=False + ) + builder.set_mem(for_loop.index, item_type, init) + for_loop.finish() + + builder.keep_alive([vec], line) + return vec + + +def vec_create_from_values( + builder: LowLevelIRBuilder, vtype: RVec, values: list[Value], line: int +) -> Value: + vec = vec_create(builder, vtype, len(values), line) + ptr = vec_items(builder, vec) + item_type = vtype.item_type + step = step_size(item_type) + for value in values: + builder.set_mem(ptr, item_type, value) + ptr = builder.int_add(ptr, step) + builder.keep_alive([vec], line) + return vec + + +def step_size(item_type: RType) -> int: + if isinstance(item_type, RPrimitive): + return item_type.size + elif isinstance(item_type, RVec): + return PLATFORM_SIZE * 2 + else: + return PLATFORM_SIZE + + +VEC_TYPE_INFO_I64: Final = 2 + + +def vec_item_type_info( + builder: LowLevelIRBuilder, typ: RType, line: int +) -> tuple[Value | None, bool, int]: + if isinstance(typ, RPrimitive) and typ.is_refcounted: + typ, src = builtin_names[typ.name] + return builder.load_address(src, typ), False, 0 + elif isinstance(typ, RInstance): + return builder.load_native_type_object(typ.name), False, 0 + elif typ in vec_item_type_tags: + return Integer(vec_item_type_tags[typ], c_size_t_rprimitive), False, 0 + elif isinstance(typ, RUnion): + non_opt = optional_value_type(typ) + assert non_opt is not None + typeval, _, _ = vec_item_type_info(builder, non_opt, line) + if typeval is not None: + return typeval, True, 0 + elif isinstance(typ, RVec): + typeval, optional, depth = vec_item_type_info(builder, typ.item_type, line) + if typeval is not None: + return typeval, optional, depth + 1 + return None, False, 0 + + +def vec_len(builder: LowLevelIRBuilder, val: Value) -> Value: + """Return len() as i64.""" + len_val = vec_len_native(builder, val) + if IS_32_BIT_PLATFORM: + return builder.coerce(len_val, int64_rprimitive, -1) + return len_val + + +def vec_len_native(builder: LowLevelIRBuilder, val: Value) -> Value: + """Return len() as platform integer type (32-bit/64-bit).""" + return builder.get_element(val, "len") + + +def vec_items(builder: LowLevelIRBuilder, vecobj: Value) -> Value: + vtype = cast(RVec, vecobj.type) + buf = builder.get_element(vecobj, "buf") + return builder.add(GetElementPtr(buf, vtype.buf_type, "items")) + + +def vec_item_ptr(builder: LowLevelIRBuilder, vecobj: Value, index: Value) -> Value: + items_addr = vec_items(builder, vecobj) + assert isinstance(vecobj.type, RVec) + # TODO: Do we need to care about alignment? + item_type = vecobj.type.item_type + if isinstance(item_type, RPrimitive): + item_size = item_type.size + elif isinstance(item_type, RVec): + item_size = 2 * PLATFORM_SIZE + else: + item_size = object_rprimitive.size + delta = builder.int_mul(index, item_size) + return builder.int_add(items_addr, delta) + + +def vec_check_and_adjust_index( + builder: LowLevelIRBuilder, lenv: Value, index: Value, line: int +) -> Value: + r = Register(int64_rprimitive) + index = builder.coerce(index, int64_rprimitive, line) + lenv = builder.coerce(lenv, int64_rprimitive, line) + ok, ok2, ok3 = BasicBlock(), BasicBlock(), BasicBlock() + fail, fail2 = BasicBlock(), BasicBlock() + is_less = builder.comparison_op(index, lenv, ComparisonOp.ULT, line) + builder.add_bool_branch(is_less, ok2, fail) + builder.activate_block(fail) + + x = builder.int_add(index, lenv) + is_less2 = builder.comparison_op(x, lenv, ComparisonOp.ULT, line) + builder.add_bool_branch(is_less2, ok, fail2) + + builder.activate_block(fail2) + # TODO: Include index in exception + builder.add(RaiseStandardError(RaiseStandardError.INDEX_ERROR, None, line)) + builder.add(Unreachable()) + + builder.activate_block(ok) + builder.assign(r, x) + builder.goto(ok3) + + builder.activate_block(ok2) + builder.assign(r, index) + builder.goto(ok3) + + builder.activate_block(ok3) + return r + + +def vec_get_item( + builder: LowLevelIRBuilder, base: Value, index: Value, line: int, *, can_borrow: bool = False +) -> Value: + """Generate inlined vec __getitem__ call. + + We inline this, since it's simple but performance-critical. + """ + # TODO: Support more item types + # TODO: Support more index types + len_val = vec_len(builder, base) + index = vec_check_and_adjust_index(builder, len_val, index, line) + return vec_get_item_unsafe(builder, base, index, line, can_borrow=can_borrow) + + +def vec_get_item_unsafe( + builder: LowLevelIRBuilder, base: Value, index: Value, line: int, *, can_borrow: bool = False +) -> Value: + """Get vec item, assuming index is non-negative and within bounds.""" + assert isinstance(base.type, RVec) + index = as_platform_int(builder, index, line) + vtype = base.type + item_addr = vec_item_ptr(builder, base, index) + result = builder.load_mem(item_addr, vtype.item_type, borrow=can_borrow) + builder.keep_alive([base], line) + return result + + +def vec_set_item( + builder: LowLevelIRBuilder, base: Value, index: Value, item: Value, line: int +) -> None: + assert isinstance(base.type, RVec) + vtype = base.type + len_val = vec_len(builder, base) + index = vec_check_and_adjust_index(builder, len_val, index, line) + index = builder.coerce(index, c_pyssize_t_rprimitive, line) + item_addr = vec_item_ptr(builder, base, index) + item_type = vtype.item_type + item = builder.coerce(item, item_type, line) + if item_type.is_refcounted: + # Read an unborrowed reference to cause a decref to be + # generated for the old item. + old_item = builder.load_mem(item_addr, item_type, borrow=True) + builder.add(DecRef(old_item)) + builder.set_mem(item_addr, item_type, item) + builder.keep_alive([base], line) + + +def vec_init_item_unsafe( + builder: LowLevelIRBuilder, base: Value, index: Value, item: Value, line: int +) -> None: + assert isinstance(base.type, RVec) + index = as_platform_int(builder, index, line) + vtype = base.type + item_addr = vec_item_ptr(builder, base, index) + item_type = vtype.item_type + item = builder.coerce(item, item_type, line) + builder.set_mem(item_addr, item_type, item) + builder.keep_alive([base], line) + + +def convert_to_t_ext_item(builder: LowLevelIRBuilder, item: Value) -> Value: + vec_len = builder.add(GetElement(item, "len")) + vec_buf = builder.add(GetElement(item, "buf")) + temp = builder.add(SetElement(Undef(VecNestedBufItem), "len", vec_len)) + return builder.add(SetElement(temp, "buf", vec_buf)) + + +def convert_from_t_ext_item(builder: LowLevelIRBuilder, item: Value, vec_type: RVec) -> Value: + """Convert a value of type VecNestedBufItem to the corresponding RVec value.""" + api_name = vec_api_by_item_type.get(vec_type.item_type) + if api_name is not None: + name = f"{api_name}.convert_from_nested" + elif isinstance(vec_type.item_type, RVec): + name = "VecNestedApi.convert_from_nested" + else: + name = "VecTApi.convert_from_nested" + + return builder.add( + CallC( + name, [item], vec_type, steals=[True], is_borrowed=False, error_kind=ERR_NEVER, line=-1 + ) + ) + + +def vec_item_type(builder: LowLevelIRBuilder, item_type: RType, line: int) -> Value: + typeobj, optional, depth = vec_item_type_info(builder, item_type, line) + assert typeobj is not None + if isinstance(typeobj, Integer): + return typeobj + else: + # Create an integer which will hold the type object * as an integral value. + # Assign implicitly coerces between pointer/integer types. + typeval: Value + typeval = Register(pointer_rprimitive) + builder.add(Assign(typeval, typeobj)) + if optional: + typeval = builder.add( + IntOp(pointer_rprimitive, typeval, Integer(1, pointer_rprimitive), IntOp.OR) + ) + return typeval + + +def vec_append(builder: LowLevelIRBuilder, vec: Value, item: Value, line: int) -> Value: + vec_type = vec.type + assert isinstance(vec_type, RVec) + item_type = vec_type.item_type + coerced_item = builder.coerce(item, item_type, line) + item_type_arg = [] + api_name = vec_api_by_item_type.get(item_type) + if api_name is not None: + name = f"{api_name}.append" + elif vec_type.depth() == 0: + name = "VecTApi.append" + item_type_arg = [vec_item_type(builder, item_type, line)] + else: + coerced_item = convert_to_t_ext_item(builder, coerced_item) + name = "VecNestedApi.append" + call = builder.add( + CallC( + name, + [vec, coerced_item] + item_type_arg, + vec_type, + steals=[True, False] + ([False] if item_type_arg else []), + is_borrowed=False, + error_kind=ERR_MAGIC, + line=line, + ) + ) + if vec_type.depth() > 0: + builder.keep_alive([item], line) + return call + + +def vec_pop(builder: LowLevelIRBuilder, base: Value, index: Value, line: int) -> Value: + assert isinstance(base.type, RVec) + vec_type = base.type + item_type = vec_type.item_type + index = as_platform_int(builder, index, line) + + api_name = vec_api_by_item_type.get(item_type) + if api_name is not None: + name = f"{api_name}.pop" + elif vec_type.depth() == 0: + name = "VecTApi.pop" + else: + name = "VecNestedApi.pop" + # Nested vecs return a generic vec struct. + item_type = VecNestedBufItem + result = builder.add( + CallC( + name, + [base, index], + RTuple([vec_type, item_type]), + steals=[True, False], + is_borrowed=False, + error_kind=ERR_MAGIC, + line=line, + ) + ) + if vec_type.depth() > 0: + orig = result + x = builder.add(TupleGet(result, 0, borrow=True)) + x = builder.add(Unborrow(x)) + y = builder.add(TupleGet(result, 1, borrow=True)) + y = builder.add(Unborrow(y)) + assert isinstance(vec_type.item_type, RVec) + z = convert_from_t_ext_item(builder, y, vec_type.item_type) + result = builder.add(TupleSet([x, z], line)) + builder.keep_alive([orig], line, steal=True) + return result + + +def vec_remove(builder: LowLevelIRBuilder, vec: Value, item: Value, line: int) -> Value: + assert isinstance(vec.type, RVec) + vec_type = vec.type + item_type = vec_type.item_type + coerced_item = builder.coerce(item, item_type, line) + + if item_type in vec_api_by_item_type: + name = f"{vec_api_by_item_type[item_type]}.remove" + elif vec_type.depth() == 0: + name = "VecTApi.remove" + else: + coerced_item = convert_to_t_ext_item(builder, coerced_item) + name = "VecNestedApi.remove" + call = builder.add( + CallC( + name, + [vec, coerced_item], + vec_type, + steals=[True, False], + is_borrowed=False, + error_kind=ERR_MAGIC, + line=line, + ) + ) + if vec_type.depth() > 0: + builder.keep_alive([item], line) + return call + + +def vec_contains(builder: LowLevelIRBuilder, vec: Value, target: Value, line: int) -> Value: + assert isinstance(vec.type, RVec) + vec_type = vec.type + item_type = vec_type.item_type + target = builder.coerce(target, item_type, line) + + step = step_size(item_type) + len_val = vec_len_native(builder, vec) + items_start = vec_items(builder, vec) + items_end = builder.int_add(items_start, builder.int_mul(len_val, step)) + + true, end = BasicBlock(), BasicBlock() + + for_loop = builder.begin_for( + items_start, items_end, Integer(step, c_pyssize_t_rprimitive), signed=False + ) + item = builder.load_mem(for_loop.index, item_type, borrow=True) + comp = builder.binary_op(item, target, "==", line) + false = BasicBlock() + builder.add(Branch(comp, true, false, Branch.BOOL)) + builder.activate_block(false) + for_loop.finish() + + builder.keep_alive([vec], line) + + res = Register(bool_rprimitive) + builder.assign(res, Integer(0, bool_rprimitive)) + builder.goto(end) + builder.activate_block(true) + builder.assign(res, Integer(1, bool_rprimitive)) + builder.goto_and_activate(end) + return res + + +def vec_slice( + builder: LowLevelIRBuilder, vec: Value, begin: Value, end: Value, line: int +) -> Value: + assert isinstance(vec.type, RVec) + vec_type = vec.type + item_type = vec_type.item_type + begin = builder.coerce(begin, int64_rprimitive, line) + end = builder.coerce(end, int64_rprimitive, line) + api_name = vec_api_by_item_type.get(item_type) + if api_name is not None: + name = f"{api_name}.slice" + elif vec_type.depth() == 0: + name = "VecTApi.slice" + else: + name = "VecNestedApi.slice" + call = CallC( + name, + [vec, begin, end], + vec_type, + steals=[False, False, False], + is_borrowed=False, + error_kind=ERR_MAGIC, + line=line, + ) + return builder.add(call) diff --git a/mypyc/lib-rt/CPy.h b/mypyc/lib-rt/CPy.h index b2a48bb29ecb..cc8b8bafaf99 100644 --- a/mypyc/lib-rt/CPy.h +++ b/mypyc/lib-rt/CPy.h @@ -608,8 +608,8 @@ static void CPy_DecRef(PyObject *p) { } CPy_NOINLINE -static void CPy_XDecRef(PyObject *p) { - CPy_XDECREF(p); +static void CPy_XDecRef(void *p) { + CPy_XDECREF((PyObject *)p); } static inline CPyTagged CPyObject_Size(PyObject *obj) { diff --git a/mypyc/lib-rt/vecs/librt_vecs.c b/mypyc/lib-rt/vecs/librt_vecs.c index c32ca1e349d1..3696e696d5c3 100644 --- a/mypyc/lib-rt/vecs/librt_vecs.c +++ b/mypyc/lib-rt/vecs/librt_vecs.c @@ -871,6 +871,11 @@ static PyObject *vec_pop(PyObject *self, PyObject *args) return res; } +// Return the base VecType for isinstance checks +static PyTypeObject *get_vec_type(void) { + return &VecType; +} + static VecCapsule Capsule = { &Vec_TAPI, &Vec_NestedAPI, @@ -880,6 +885,7 @@ static VecCapsule Capsule = { &Vec_U8API, &Vec_FloatAPI, &Vec_BoolAPI, + get_vec_type, }; #endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/vecs/librt_vecs.h b/mypyc/lib-rt/vecs/librt_vecs.h index 2f88dc51201b..80dc977c1914 100644 --- a/mypyc/lib-rt/vecs/librt_vecs.h +++ b/mypyc/lib-rt/vecs/librt_vecs.h @@ -4,6 +4,10 @@ // Header for the implementation of librt.vecs, which defines the 'vec' type. // Refer to librt_vecs.c for more detailed information. +#define PY_SSIZE_T_CLEAN +#include +#include + #ifndef MYPYC_EXPERIMENTAL static int @@ -15,10 +19,6 @@ import_librt_vecs(void) #else // MYPYC_EXPERIMENTAL -#define PY_SSIZE_T_CLEAN -#include -#include - // Magic (native) integer return value on exception. Caller must also // use PyErr_Occurred() since this overlaps with valid integer values. #define MYPYC_INT_ERROR -113 @@ -460,6 +460,7 @@ typedef struct { VecU8API *u8; VecFloatAPI *float_; VecBoolAPI *bool_; + PyTypeObject *(*get_vec_type)(void); // Function to get base VecType for isinstance checks } VecCapsule; #define VEC_BUF_SIZE(b) ((b)->ob_base.ob_size) @@ -835,6 +836,38 @@ int Vec_GenericRemove(Py_ssize_t *len, PyObject **items, PyObject *item); PyObject *Vec_GenericPopWrapper(Py_ssize_t *len, PyObject **items, PyObject *args); PyObject *Vec_GenericPop(Py_ssize_t *len, PyObject **items, Py_ssize_t index); +// Global API pointers initialized by import_librt_vecs() +static VecCapsule *VecApi; +static VecI64API VecI64Api; +static VecI32API VecI32Api; +static VecI16API VecI16Api; +static VecU8API VecU8Api; +static VecFloatAPI VecFloatApi; +static VecBoolAPI VecBoolApi; +static VecTAPI VecTApi; +static VecNestedAPI VecNestedApi; + +static int +import_librt_vecs(void) +{ + PyObject *mod = PyImport_ImportModule("librt.vecs"); + if (mod == NULL) + return -1; + Py_DECREF(mod); // we import just for the side effect of making the below work. + VecApi = PyCapsule_Import("librt.vecs._C_API", 0); + if (!VecApi) + return -1; + VecI64Api = *VecApi->i64; + VecI32Api = *VecApi->i32; + VecI16Api = *VecApi->i16; + VecU8Api = *VecApi->u8; + VecFloatApi = *VecApi->float_; + VecBoolApi = *VecApi->bool_; + VecTApi = *VecApi->t; + VecNestedApi = *VecApi->nested; + return 0; +} + #endif // MYPYC_EXPERIMENTAL #endif // VEC_H_INCL diff --git a/mypyc/lib-rt/vecs/vec_t.c b/mypyc/lib-rt/vecs/vec_t.c index 4c2b77f75dd4..8a03ea1a2e64 100644 --- a/mypyc/lib-rt/vecs/vec_t.c +++ b/mypyc/lib-rt/vecs/vec_t.c @@ -128,6 +128,8 @@ VecT VecT_Slice(VecT vec, int64_t start, int64_t end) { if (end > vec.len) end = vec.len; int64_t slicelength = end - start; + if (slicelength == 0) + return (VecT) { .len = 0, .buf = NULL }; VecT res = vec_alloc(slicelength, vec.buf->item_type); if (VEC_IS_ERROR(res)) return res; diff --git a/mypyc/lib-rt/vecs_extra_ops.c b/mypyc/lib-rt/vecs_extra_ops.c new file mode 100644 index 000000000000..564eae2c9fcb --- /dev/null +++ b/mypyc/lib-rt/vecs_extra_ops.c @@ -0,0 +1,10 @@ +// Primitives related to librt.vecs that get linked statically +// with compiled modules, instead of being called via a capsule. + +#include "vecs_extra_ops.h" + +#ifdef MYPYC_EXPERIMENTAL + +// All operations are currently implemented as inline functions in vecs_extra_ops.h + +#endif // MYPYC_EXPERIMENTAL diff --git a/mypyc/lib-rt/vecs_extra_ops.h b/mypyc/lib-rt/vecs_extra_ops.h new file mode 100644 index 000000000000..b8f72876b129 --- /dev/null +++ b/mypyc/lib-rt/vecs_extra_ops.h @@ -0,0 +1,15 @@ +#ifndef BYTESWRITER_EXTRA_OPS_H +#define BYTESWRITER_EXTRA_OPS_H + +#ifdef MYPYC_EXPERIMENTAL + +#include "vecs/librt_vecs.h" + +// Check if obj is an instance of vec (any vec type) +static inline int CPyVec_Check(PyObject *obj) { + return PyObject_TypeCheck(obj, VecApi->get_vec_type()); +} + +#endif // MYPYC_EXPERIMENTAL + +#endif diff --git a/mypyc/primitives/librt_vecs_ops.py b/mypyc/primitives/librt_vecs_ops.py new file mode 100644 index 000000000000..4688aea14aae --- /dev/null +++ b/mypyc/primitives/librt_vecs_ops.py @@ -0,0 +1,15 @@ +from mypyc.ir.deps import LIBRT_VECS, VECS_EXTRA_OPS +from mypyc.ir.ops import ERR_NEVER +from mypyc.ir.rtypes import bit_rprimitive, object_rprimitive +from mypyc.primitives.registry import function_op + +# isinstance(obj, vec) +isinstance_vec = function_op( + name="builtins.isinstance", + arg_types=[object_rprimitive], + return_type=bit_rprimitive, + c_function_name="CPyVec_Check", + error_kind=ERR_NEVER, + experimental=True, + dependencies=[LIBRT_VECS, VECS_EXTRA_OPS], +) diff --git a/mypyc/primitives/list_ops.py b/mypyc/primitives/list_ops.py index b9d20a25bea3..efd40469606f 100644 --- a/mypyc/primitives/list_ops.py +++ b/mypyc/primitives/list_ops.py @@ -151,7 +151,7 @@ priority=6, ) -# This is unsafe because it assumes that the index is a non-negative short integer +# This is unsafe because it assumes that the index is a non-negative integer # that is in-bounds for the list. list_get_item_unsafe_op = custom_primitive_op( name="list_get_item_unsafe", diff --git a/mypyc/rt_subtype.py b/mypyc/rt_subtype.py index 004e56ed75bc..54205a1de698 100644 --- a/mypyc/rt_subtype.py +++ b/mypyc/rt_subtype.py @@ -24,6 +24,7 @@ RType, RTypeVisitor, RUnion, + RVec, RVoid, is_bit_rprimitive, is_bool_rprimitive, @@ -34,6 +35,8 @@ def is_runtime_subtype(left: RType, right: RType) -> bool: + if isinstance(right, RUnion) and not isinstance(left, RUnion): + return any(not item.is_unboxed and is_runtime_subtype(left, item) for item in right.items) return left.accept(RTSubtypeVisitor(right)) @@ -50,6 +53,10 @@ def __init__(self, right: RType) -> None: def visit_rinstance(self, left: RInstance) -> bool: return is_subtype(left, self.right) + def visit_rvec(self, left: RVec) -> bool: + # TODO: Better implementation + return left == self.right + def visit_runion(self, left: RUnion) -> bool: return not self.right.is_unboxed and is_subtype(left, self.right) diff --git a/mypyc/sametype.py b/mypyc/sametype.py index 1b811d4e9041..d6e33f81cccd 100644 --- a/mypyc/sametype.py +++ b/mypyc/sametype.py @@ -12,6 +12,7 @@ RType, RTypeVisitor, RUnion, + RVec, RVoid, ) @@ -50,6 +51,9 @@ def __init__(self, right: RType) -> None: def visit_rinstance(self, left: RInstance) -> bool: return isinstance(self.right, RInstance) and left.name == self.right.name + def visit_rvec(self, left: RVec) -> bool: + return isinstance(self.right, RVec) and left.item_type == self.right.item_type + def visit_runion(self, left: RUnion) -> bool: if isinstance(self.right, RUnion): items = list(self.right.items) diff --git a/mypyc/subtype.py b/mypyc/subtype.py index 3d5fa1fb4fca..085cfc4f60a4 100644 --- a/mypyc/subtype.py +++ b/mypyc/subtype.py @@ -11,6 +11,7 @@ RType, RTypeVisitor, RUnion, + RVec, RVoid, is_bit_rprimitive, is_bool_rprimitive, @@ -54,6 +55,10 @@ def __init__(self, right: RType, *, relaxed: bool = False) -> None: def visit_rinstance(self, left: RInstance) -> bool: return isinstance(self.right, RInstance) and self.right.class_ir in left.class_ir.mro + def visit_rvec(self, left: RVec) -> bool: + # TODO: Better implementation + return left == self.right + def visit_runion(self, left: RUnion) -> bool: return all(is_subtype(item, self.right, relaxed=self.relaxed) for item in left.items) diff --git a/mypyc/test-data/alwaysdefined.test b/mypyc/test-data/alwaysdefined.test index ecbc8c410d6d..9c9b15e4e122 100644 --- a/mypyc/test-data/alwaysdefined.test +++ b/mypyc/test-data/alwaysdefined.test @@ -729,3 +729,13 @@ class NestedFunc: -- TODO: Support nested functions. NestedFunc: [] f___init___NestedFunc_obj: [] + +[case testAlwaysDefinedWithVecSetItem] +from librt.vecs import vec + +class C: + def __init__(self, v: vec[C]) -> None: + self.v = v + self.v[0] = self +[out] +C: [v] diff --git a/mypyc/test-data/capsule-deps.test b/mypyc/test-data/capsule-deps.test index 063be3d6261d..6e70927538bf 100644 --- a/mypyc/test-data/capsule-deps.test +++ b/mypyc/test-data/capsule-deps.test @@ -87,3 +87,35 @@ def f() -> bytes: [out] Capsule(name='librt.base64') Capsule(name='librt.strings') + +[case testVecCapsuleDepInFunction_experimental] +from librt.vecs import vec + +def f() -> None: + vec[str]() +[out] +Capsule(name='librt.vecs') + +[case testVecCapsuleDepInMethod_experimental] +from librt.vecs import vec + +class C: + def m(self) -> None: + vec[str]() +[out] +Capsule(name='librt.vecs') + +[case testVecCapsuleDepAtTopLevel_experimental] +from librt.vecs import vec + +vec[str]() +[out] +Capsule(name='librt.vecs') + +[case testVecCapsuleDepInAttributeType_experimental] +from librt.vecs import vec + +class C: + v: vec[str] +[out] +Capsule(name='librt.vecs') diff --git a/mypyc/test-data/exceptions.test b/mypyc/test-data/exceptions.test index 9e097a18d103..c271c9ae63d5 100644 --- a/mypyc/test-data/exceptions.test +++ b/mypyc/test-data/exceptions.test @@ -719,3 +719,27 @@ L2: L3: r5 = :: float return r5 + +[case testVecI64Remove] +from librt.vecs import vec, remove +from mypy_extensions import i64 + +def f(v: vec[i64], i: i64, n: i64) -> None: + v = remove(v, n) +[out] +def f(v, i, n): + v :: vec[i64] + i, n :: i64 + r0 :: vec[i64] + r1 :: None +L0: + inc_ref v + r0 = VecI64Api.remove(v, n) + if is_error(r0) goto L2 (error at f:5) else goto L1 +L1: + v = r0 + dec_ref v + return 1 +L2: + r1 = :: None + return r1 diff --git a/mypyc/test-data/irbuild-vec-i64.test b/mypyc/test-data/irbuild-vec-i64.test new file mode 100644 index 000000000000..a8d7eacda7e5 --- /dev/null +++ b/mypyc/test-data/irbuild-vec-i64.test @@ -0,0 +1,822 @@ +-- Test cases for vec[i64]. These also partially cover other unboxed item types, +-- which use a similar runtime representation. + +[case testVecI64CreateEmpty] +from librt.vecs import vec, append +from mypy_extensions import i64 + +def f() -> vec[i64]: + return vec[i64]() +[out] +def f(): + r0 :: vec[i64] +L0: + r0 = VecI64Api.alloc(0, 0) + return r0 + +[case testVecI64Len_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64]) -> i64: + l = len(v) + return l +[out] +def f(v): + v :: vec[i64] + r0 :: native_int + l :: i64 +L0: + r0 = v.len + l = r0 + return l + +[case testVecI64Len_32bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64]) -> i64: + l = len(v) + return l +[out] +def f(v): + v :: vec[i64] + r0 :: native_int + r1, l :: i64 +L0: + r0 = v.len + r1 = extend signed r0: native_int to i64 + l = r1 + return l + +[case testVecI64GetItem_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64], i: i64) -> i64: + return v[i] +[out] +def f(v, i): + v :: vec[i64] + i :: i64 + r0 :: native_int + r1 :: bit + r2 :: i64 + r3 :: bit + r4 :: bool + r5 :: i64 + r6 :: object + r7 :: ptr + r8 :: i64 + r9 :: ptr + r10 :: i64 +L0: + r0 = v.len + r1 = i < r0 :: unsigned + if r1 goto L4 else goto L1 :: bool +L1: + r2 = i + r0 + r3 = r2 < r0 :: unsigned + if r3 goto L3 else goto L2 :: bool +L2: + r4 = raise IndexError + unreachable +L3: + r5 = r2 + goto L5 +L4: + r5 = i +L5: + r6 = v.buf + r7 = get_element_ptr r6 items :: VecI64BufObject + r8 = r5 * 8 + r9 = r7 + r8 + r10 = load_mem r9 :: i64* + keep_alive v + return r10 + +[case testVecI64GetItem_32bit] +# The IR is quite verbose, but it's acceptable since 32-bit targets are not common any more +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64], i: i64) -> i64: + return v[i] +[out] +def f(v, i): + v :: vec[i64] + i :: i64 + r0 :: native_int + r1 :: i64 + r2 :: bit + r3 :: i64 + r4 :: bit + r5 :: bool + r6 :: i64 + r7, r8 :: bit + r9 :: native_int + r10 :: object + r11 :: ptr + r12 :: native_int + r13 :: ptr + r14 :: i64 +L0: + r0 = v.len + r1 = extend signed r0: native_int to i64 + r2 = i < r1 :: unsigned + if r2 goto L4 else goto L1 :: bool +L1: + r3 = i + r1 + r4 = r3 < r1 :: unsigned + if r4 goto L3 else goto L2 :: bool +L2: + r5 = raise IndexError + unreachable +L3: + r6 = r3 + goto L5 +L4: + r6 = i +L5: + r7 = r6 < 2147483648 :: signed + if r7 goto L6 else goto L8 :: bool +L6: + r8 = r6 >= -2147483648 :: signed + if r8 goto L7 else goto L8 :: bool +L7: + r9 = truncate r6: i64 to native_int + goto L9 +L8: + CPyInt32_Overflow() + unreachable +L9: + r10 = v.buf + r11 = get_element_ptr r10 items :: VecI64BufObject + r12 = r9 * 8 + r13 = r11 + r12 + r14 = load_mem r13 :: i64* + keep_alive v + return r14 + +[case testVecI64Append] +from librt.vecs import vec, append +from mypy_extensions import i64 + +def f(v: vec[i64], i: i64) -> vec[i64]: + return append(v, i) +[out] +def f(v, i): + v :: vec[i64] + i :: i64 + r0 :: vec[i64] +L0: + r0 = VecI64Api.append(v, i) + return r0 + +[case testVecI64SetItem_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64], i: i64, x: i64) -> None: + v[i] = x +[out] +def f(v, i, x): + v :: vec[i64] + i, x :: i64 + r0 :: native_int + r1 :: bit + r2 :: i64 + r3 :: bit + r4 :: bool + r5 :: i64 + r6 :: object + r7 :: ptr + r8 :: i64 + r9 :: ptr +L0: + r0 = v.len + r1 = i < r0 :: unsigned + if r1 goto L4 else goto L1 :: bool +L1: + r2 = i + r0 + r3 = r2 < r0 :: unsigned + if r3 goto L3 else goto L2 :: bool +L2: + r4 = raise IndexError + unreachable +L3: + r5 = r2 + goto L5 +L4: + r5 = i +L5: + r6 = v.buf + r7 = get_element_ptr r6 items :: VecI64BufObject + r8 = r5 * 8 + r9 = r7 + r8 + set_mem r9, x :: i64* + keep_alive v + return 1 + +[case testVecI64ConstructFromListExpr] +from librt.vecs import vec +from mypy_extensions import i64 + +def f() -> vec[i64]: + return vec[i64]([1, 5, 14]) +[out] +def f(): + r0 :: vec[i64] + r1 :: object + r2, r3, r4, r5 :: ptr +L0: + r0 = VecI64Api.alloc(3, 3) + r1 = r0.buf + r2 = get_element_ptr r1 items :: VecI64BufObject + set_mem r2, 1 :: i64* + r3 = r2 + 8 + set_mem r3, 5 :: i64* + r4 = r3 + 8 + set_mem r4, 14 :: i64* + r5 = r4 + 8 + keep_alive r0 + return r0 + +[case testVecI64ConstructFromListMultiply_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(n: i64) -> vec[i64]: + return vec[i64]([3] * n) +[out] +def f(n): + n :: i64 + r0 :: vec[i64] + r1 :: object + r2 :: ptr + r3 :: i64 + r4, r5 :: ptr + r6 :: bit + r7 :: ptr +L0: + r0 = VecI64Api.alloc(n, n) + r1 = r0.buf + r2 = get_element_ptr r1 items :: VecI64BufObject + r3 = n * 8 + r4 = r2 + r3 + r5 = r2 +L1: + r6 = r5 < r4 :: unsigned + if r6 goto L2 else goto L3 :: bool +L2: + set_mem r5, 3 :: i64* + r7 = r5 + 8 + r5 = r7 + goto L1 +L3: + keep_alive r0 + return r0 + +[case testVecI64ConstructFromListMultiply2] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(n: i64, x: i64) -> vec[i64]: + return vec[i64]([x] * 3) +[out] +def f(n, x): + n, x :: i64 + r0 :: vec[i64] + r1 :: object + r2 :: ptr + r3 :: native_int + r4, r5 :: ptr + r6 :: bit + r7 :: ptr +L0: + r0 = VecI64Api.alloc(3, 3) + r1 = r0.buf + r2 = get_element_ptr r1 items :: VecI64BufObject + r3 = 3 * 8 + r4 = r2 + r3 + r5 = r2 +L1: + r6 = r5 < r4 :: unsigned + if r6 goto L2 else goto L3 :: bool +L2: + set_mem r5, x :: i64* + r7 = r5 + 8 + r5 = r7 + goto L1 +L3: + keep_alive r0 + return r0 + +[case testVecI64ConstructFromListComprehension] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(n: i64) -> vec[i64]: + # TODO: Can we pre-allocate a vec with capacity 5? + return vec[i64]([x + 1 for x in range(i64(5))]) +[out] +def f(n): + n :: i64 + r0, r1 :: vec[i64] + r2, x :: i64 + r3 :: bit + r4 :: i64 + r5 :: vec[i64] + r6 :: i64 +L0: + r0 = VecI64Api.alloc(0, 0) + r1 = r0 + r2 = 0 + x = r2 +L1: + r3 = r2 < 5 :: signed + if r3 goto L2 else goto L4 :: bool +L2: + r4 = x + 1 + r5 = VecI64Api.append(r1, r4) + r1 = r5 +L3: + r6 = r2 + 1 + r2 = r6 + x = r6 + goto L1 +L4: + return r1 + +[case testVecI64FastComprehensionFromList] +from librt.vecs import vec +from mypy_extensions import i64 +from typing import List + +def f(n: i64, l: List[i64]) -> vec[i64]: + return vec[i64]([x + 1 for x in l]) +[out] +def f(n, l): + n :: i64 + l :: list + r0 :: native_int + r1 :: vec[i64] + r2, r3 :: native_int + r4 :: bit + r5 :: object + r6, x, r7 :: i64 + r8 :: object + r9 :: ptr + r10 :: native_int + r11 :: ptr + r12 :: native_int +L0: + r0 = var_object_size l + r1 = VecI64Api.alloc(r0, r0) + r2 = 0 +L1: + r3 = var_object_size l + r4 = r2 < r3 :: signed + if r4 goto L2 else goto L4 :: bool +L2: + r5 = list_get_item_unsafe l, r2 + r6 = unbox(i64, r5) + x = r6 + r7 = x + 1 + r8 = r1.buf + r9 = get_element_ptr r8 items :: VecI64BufObject + r10 = r2 * 8 + r11 = r9 + r10 + set_mem r11, r7 :: i64* + keep_alive r1 +L3: + r12 = r2 + 1 + r2 = r12 + goto L1 +L4: + return r1 + +[case testVecI64FastComprehensionFromVec] +from librt.vecs import vec +from mypy_extensions import i64 +from typing import List + +def f(n: i64, v: vec[i64]) -> vec[i64]: + return vec[i64]([x + 1 for x in v]) +[out] +def f(n, v): + n :: i64 + v :: vec[i64] + r0 :: native_int + r1 :: vec[i64] + r2, r3 :: native_int + r4 :: bit + r5 :: object + r6 :: ptr + r7 :: native_int + r8 :: ptr + r9, x, r10 :: i64 + r11 :: object + r12 :: ptr + r13 :: native_int + r14 :: ptr + r15 :: native_int +L0: + r0 = v.len + r1 = VecI64Api.alloc(r0, r0) + r2 = 0 +L1: + r3 = v.len + r4 = r2 < r3 :: signed + if r4 goto L2 else goto L4 :: bool +L2: + r5 = v.buf + r6 = get_element_ptr r5 items :: VecI64BufObject + r7 = r2 * 8 + r8 = r6 + r7 + r9 = load_mem r8 :: i64* + keep_alive v + x = r9 + r10 = x + 1 + r11 = r1.buf + r12 = get_element_ptr r11 items :: VecI64BufObject + r13 = r2 * 8 + r14 = r12 + r13 + set_mem r14, r10 :: i64* + keep_alive r1 +L3: + r15 = r2 + 1 + r2 = r15 + goto L1 +L4: + return r1 + +[case testVecI64ConstructFromRange] +from librt.vecs import vec, pop +from mypy_extensions import i64 + +def f() -> vec[i64]: + # TODO: Can we pre-allocate a vec with capacity 7? + return vec[i64](range(7)) +[out] +def f(): + r0, r1 :: vec[i64] + r2 :: short_int + r3, ___tmp_6 :: i64 + r4 :: bit + r5 :: vec[i64] + r6 :: short_int + r7 :: i64 +L0: + r0 = VecI64Api.alloc(0, 0) + r1 = r0 + r2 = 0 + r3 = r2 >> 1 + ___tmp_6 = r3 +L1: + r4 = int_lt r2, 14 + if r4 goto L2 else goto L4 :: bool +L2: + r5 = VecI64Api.append(r1, ___tmp_6) + r1 = r5 +L3: + r6 = r2 + 2 + r2 = r6 + r7 = r6 >> 1 + ___tmp_6 = r7 + goto L1 +L4: + return r1 + +[case testVecI64ForLoop] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64]) -> i64: + t: i64 = 0 + for x in v: + t += 1 + return t +[out] +def f(v): + v :: vec[i64] + t :: i64 + r0, r1 :: native_int + r2 :: bit + r3 :: object + r4 :: ptr + r5 :: native_int + r6 :: ptr + r7, x, r8 :: i64 + r9 :: native_int +L0: + t = 0 + r0 = 0 +L1: + r1 = v.len + r2 = r0 < r1 :: signed + if r2 goto L2 else goto L4 :: bool +L2: + r3 = v.buf + r4 = get_element_ptr r3 items :: VecI64BufObject + r5 = r0 * 8 + r6 = r4 + r5 + r7 = load_mem r6 :: i64* + keep_alive v + x = r7 + r8 = t + 1 + t = r8 +L3: + r9 = r0 + 1 + r0 = r9 + goto L1 +L4: + return t + +[case testVecI64Contains] +from librt.vecs import vec +from mypy_extensions import i64 + +def contains(v: vec[i64], n: i64) -> bool: + return n in v +[out] +def contains(v, n): + v :: vec[i64] + n :: i64 + r0 :: native_int + r1 :: object + r2 :: ptr + r3 :: native_int + r4, r5 :: ptr + r6 :: bit + r7 :: i64 + r8 :: bit + r9 :: ptr + r10 :: bool +L0: + r0 = v.len + r1 = v.buf + r2 = get_element_ptr r1 items :: VecI64BufObject + r3 = r0 * 8 + r4 = r2 + r3 + r5 = r2 +L1: + r6 = r5 < r4 :: unsigned + if r6 goto L2 else goto L4 :: bool +L2: + r7 = load_mem r5 :: i64* + r8 = r7 == n + if r8 goto L5 else goto L3 :: bool +L3: + r9 = r5 + 8 + r5 = r9 + goto L1 +L4: + keep_alive v + r10 = 0 + goto L6 +L5: + r10 = 1 +L6: + return r10 + +[case testVecI64GetItemWithInt_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64]) -> i64: + return v[0] +[out] +def f(v): + v :: vec[i64] + r0 :: native_int + r1 :: bit + r2 :: i64 + r3 :: bit + r4 :: bool + r5 :: i64 + r6 :: object + r7 :: ptr + r8 :: i64 + r9 :: ptr + r10 :: i64 +L0: + r0 = v.len + r1 = 0 < r0 :: unsigned + if r1 goto L4 else goto L1 :: bool +L1: + r2 = 0 + r0 + r3 = r2 < r0 :: unsigned + if r3 goto L3 else goto L2 :: bool +L2: + r4 = raise IndexError + unreachable +L3: + r5 = r2 + goto L5 +L4: + r5 = 0 +L5: + r6 = v.buf + r7 = get_element_ptr r6 items :: VecI64BufObject + r8 = r5 * 8 + r9 = r7 + r8 + r10 = load_mem r9 :: i64* + keep_alive v + return r10 + +[case testVecI64Slicing_64bit] +from librt.vecs import vec +from mypy_extensions import i64 + +def f(v: vec[i64], n: i64, m: i64) -> None: + a = v[:] + b = v[n:] + c = v[n:m] + d = v[:m] + e = v[1:-2] +[out] +def f(v, n, m): + v :: vec[i64] + n, m :: i64 + r0, a, r1, b, r2, c, r3, d, r4, e :: vec[i64] +L0: + r0 = VecI64Api.slice(v, 0, 4611686018427387903) + a = r0 + r1 = VecI64Api.slice(v, n, 4611686018427387903) + b = r1 + r2 = VecI64Api.slice(v, n, m) + c = r2 + r3 = VecI64Api.slice(v, 0, m) + d = r3 + r4 = VecI64Api.slice(v, 1, -2) + e = r4 + return 1 + +[case testVecI64Remove] +from librt.vecs import vec, remove +from mypy_extensions import i64 + +def rem(v: vec[i64], n: i64) -> None: + v = remove(v, n) +[out] +def rem(v, n): + v :: vec[i64] + n :: i64 + r0 :: vec[i64] +L0: + r0 = VecI64Api.remove(v, n) + v = r0 + return 1 + +[case testVecI64PopLast] +from typing import Tuple +from librt.vecs import vec, pop +from mypy_extensions import i64 + +def pop_last(v: vec[i64]) -> Tuple[vec[i64], i64]: + return pop(v) +[out] +def pop_last(v): + v :: vec[i64] + r0 :: tuple[vec[i64], i64] +L0: + r0 = VecI64Api.pop(v, -1) + return r0 + +[case testVecI64PopNth_64bit] +from typing import Tuple +from librt.vecs import vec, pop +from mypy_extensions import i64 + +def pop_nth(v: vec[i64], n: i64) -> Tuple[vec[i64], i64]: + return pop(v, n) +[out] +def pop_nth(v, n): + v :: vec[i64] + n :: i64 + r0 :: tuple[vec[i64], i64] +L0: + r0 = VecI64Api.pop(v, n) + return r0 + +[case testVecI64InPlaceOp_64bit] +from librt.vecs import vec, remove +from mypy_extensions import i64 + +def inplace(v: vec[i64], n: i64, m: i64) -> None: + v[n] += m +[out] +def inplace(v, n, m): + v :: vec[i64] + n, m :: i64 + r0 :: native_int + r1 :: bit + r2 :: i64 + r3 :: bit + r4 :: bool + r5 :: i64 + r6 :: object + r7 :: ptr + r8 :: i64 + r9 :: ptr + r10, r11 :: i64 + r12 :: native_int + r13 :: bit + r14 :: i64 + r15 :: bit + r16 :: bool + r17 :: i64 + r18 :: object + r19 :: ptr + r20 :: i64 + r21 :: ptr +L0: + r0 = v.len + r1 = n < r0 :: unsigned + if r1 goto L4 else goto L1 :: bool +L1: + r2 = n + r0 + r3 = r2 < r0 :: unsigned + if r3 goto L3 else goto L2 :: bool +L2: + r4 = raise IndexError + unreachable +L3: + r5 = r2 + goto L5 +L4: + r5 = n +L5: + r6 = v.buf + r7 = get_element_ptr r6 items :: VecI64BufObject + r8 = r5 * 8 + r9 = r7 + r8 + r10 = load_mem r9 :: i64* + keep_alive v + r11 = r10 + m + r12 = v.len + r13 = n < r12 :: unsigned + if r13 goto L9 else goto L6 :: bool +L6: + r14 = n + r12 + r15 = r14 < r12 :: unsigned + if r15 goto L8 else goto L7 :: bool +L7: + r16 = raise IndexError + unreachable +L8: + r17 = r14 + goto L10 +L9: + r17 = n +L10: + r18 = v.buf + r19 = get_element_ptr r18 items :: VecI64BufObject + r20 = r17 * 8 + r21 = r19 + r20 + set_mem r21, r11 :: i64* + keep_alive v + return 1 + +[case testVecI64Narrow] +from librt.vecs import vec +from mypy_extensions import i64 +from typing import Optional, Union + +def f(x: Optional[vec[i64]]) -> None: + if x is not None: + len(x) + +def g(x: Union[vec[i64], str]) -> None: + if isinstance(x, vec): + len(x) +[out] +def f(x): + x :: union[vec[i64], None] + r0 :: object + r1 :: bit + r2 :: vec[i64] + r3 :: native_int +L0: + r0 = load_address _Py_NoneStruct + r1 = x != r0 + if r1 goto L1 else goto L2 :: bool +L1: + r2 = unbox(vec[i64], x) + r3 = r2.len +L2: + return 1 +def g(x): + x :: union[vec[i64], str] + r0 :: bit + r1 :: vec[i64] + r2 :: native_int +L0: + r0 = CPyVec_Check(x) + if r0 goto L1 else goto L2 :: bool +L1: + r1 = unbox(vec[i64], x) + r2 = r1.len +L2: + return 1 diff --git a/mypyc/test/test_capsule_deps.py b/mypyc/test/test_capsule_deps.py index 84ba221dc766..e8b1c4a79057 100644 --- a/mypyc/test/test_capsule_deps.py +++ b/mypyc/test/test_capsule_deps.py @@ -7,14 +7,13 @@ from mypy.errors import CompileError from mypy.test.config import test_temp_dir from mypy.test.data import DataDrivenTestCase -from mypyc.analysis.capsule_deps import find_implicit_op_dependencies -from mypyc.common import TOP_LEVEL_NAME +from mypyc.analysis.capsule_deps import find_class_dependencies, find_implicit_op_dependencies from mypyc.options import CompilerOptions from mypyc.test.testutil import ( ICODE_GEN_BUILTINS, MypycDataSuite, assert_test_output, - build_ir_for_single_file, + build_ir_for_single_file2, infer_ir_build_options_from_test_name, use_custom_builtins, ) @@ -34,20 +33,23 @@ def run_case(self, testcase: DataDrivenTestCase) -> None: return with use_custom_builtins(os.path.join(self.data_prefix, ICODE_GEN_BUILTINS), testcase): try: - ir = build_ir_for_single_file(testcase.input, options) + module_ir, _, _, _ = build_ir_for_single_file2(testcase.input, options) except CompileError as e: actual = e.messages else: all_deps: set[str] = set() - for fn in ir: - if fn.name == TOP_LEVEL_NAME and not testcase.name.endswith("_toplevel"): - continue + for fn in module_ir.functions: compiler_options = CompilerOptions() lower_ir(fn, compiler_options) deps = find_implicit_op_dependencies(fn) if deps: for dep in deps: all_deps.add(repr(dep)) + for cl in module_ir.classes: + deps = find_class_dependencies(cl) + if deps: + for dep in deps: + all_deps.add(repr(dep)) actual = sorted(all_deps) if all_deps else ["No deps"] assert_test_output(testcase, actual, "Invalid test output", testcase.output) diff --git a/mypyc/test/test_cheader.py b/mypyc/test/test_cheader.py index 82223a0c451f..d955e51e33a6 100644 --- a/mypyc/test/test_cheader.py +++ b/mypyc/test/test_cheader.py @@ -17,6 +17,8 @@ float_ops, generic_ops, int_ops, + librt_strings_ops, + librt_vecs_ops, list_ops, misc_ops, registry, @@ -65,6 +67,8 @@ def check_name(name: str) -> None: float_ops, set_ops, weakref_ops, + librt_vecs_ops, + librt_strings_ops, ]: for name in dir(module): val = getattr(module, name, None) diff --git a/mypyc/test/test_irbuild.py b/mypyc/test/test_irbuild.py index 707ee1142862..4874a6205103 100644 --- a/mypyc/test/test_irbuild.py +++ b/mypyc/test/test_irbuild.py @@ -44,6 +44,7 @@ "irbuild-i32.test", "irbuild-i16.test", "irbuild-u8.test", + "irbuild-vec-i64.test", "irbuild-vectorcall.test", "irbuild-unreachable.test", "irbuild-isinstance.test",