Skip to content

Commit 87f98db

Browse files
[mypyc] Add codegen support and compiled run tests for vecs (#20737)
Now vecs work efficiently in compiled code (when experimental features are enabled). This is the last big PR from my old vec branch. The following PRs will be more incremental. Follow-up to #20732. Related issue: mypyc/mypyc#840 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 7fdbed0 commit 87f98db

File tree

10 files changed

+1924
-38
lines changed

10 files changed

+1924
-38
lines changed

mypyc/codegen/emit.py

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@
2121
REG_PREFIX,
2222
STATIC_PREFIX,
2323
TYPE_PREFIX,
24+
TYPE_VAR_PREFIX,
2425
)
2526
from mypyc.ir.class_ir import ClassIR, all_concrete_classes
2627
from mypyc.ir.func_ir import FUNC_STATICMETHOD, FuncDecl, FuncIR, get_text_signature
27-
from mypyc.ir.ops import BasicBlock, Value
28+
from mypyc.ir.ops import (
29+
NAMESPACE_MODULE,
30+
NAMESPACE_STATIC,
31+
NAMESPACE_TYPE,
32+
NAMESPACE_TYPE_VAR,
33+
BasicBlock,
34+
Value,
35+
)
2836
from mypyc.ir.rtypes import (
2937
RInstance,
3038
RPrimitive,
3139
RTuple,
3240
RType,
3341
RUnion,
42+
RVec,
3443
int_rprimitive,
3544
is_bool_or_bit_rprimitive,
3645
is_bytearray_rprimitive,
@@ -56,14 +65,24 @@
5665
is_uint8_rprimitive,
5766
object_rprimitive,
5867
optional_value_type,
68+
vec_api_by_item_type,
69+
vec_item_type_tags,
5970
)
6071
from mypyc.namegen import NameGenerator, exported_name
72+
from mypyc.primitives.registry import builtin_names
6173
from mypyc.sametype import is_same_type
6274

6375
# Whether to insert debug asserts for all error handling, to quickly
6476
# catch errors propagating without exceptions set.
6577
DEBUG_ERRORS: Final = False
6678

79+
PREFIX_MAP: Final = {
80+
NAMESPACE_STATIC: STATIC_PREFIX,
81+
NAMESPACE_TYPE: TYPE_PREFIX,
82+
NAMESPACE_MODULE: MODULE_PREFIX,
83+
NAMESPACE_TYPE_VAR: TYPE_VAR_PREFIX,
84+
}
85+
6786

6887
class HeaderDeclaration:
6988
"""A representation of a declaration in C.
@@ -326,13 +345,22 @@ def ctype_spaced(self, rtype: RType) -> str:
326345
else:
327346
return ctype + " "
328347

348+
def set_undefined_value(self, target: str, rtype: RType) -> None:
349+
if isinstance(rtype, RVec):
350+
self.emit_line(f"{target}.len = -1;")
351+
self.emit_line(f"{target}.buf = NULL;")
352+
else:
353+
self.emit_line(f"{target} = {self.c_undefined_value(rtype)};")
354+
329355
def c_undefined_value(self, rtype: RType) -> str:
330356
if not rtype.is_unboxed:
331357
return "NULL"
332358
elif isinstance(rtype, RPrimitive):
333359
return rtype.c_undefined
334360
elif isinstance(rtype, RTuple):
335361
return self.tuple_undefined_value(rtype)
362+
elif isinstance(rtype, RVec):
363+
return f"({self.ctype(rtype)}) {{ -1, NULL }}"
336364
assert False, rtype
337365

338366
def c_error_value(self, rtype: RType) -> str:
@@ -435,6 +463,12 @@ def error_value_check(self, rtype: RType, value: str, compare: str) -> str:
435463
return self.tuple_undefined_check_cond(
436464
rtype, value, self.c_error_value, compare, check_exception=False
437465
)
466+
elif isinstance(rtype, RVec):
467+
if compare == "==":
468+
return f"{value}.len < 0"
469+
elif compare == "!=":
470+
return f"{value}.len >= 0"
471+
assert False, compare
438472
else:
439473
return f"{value} {compare} {self.c_error_value(rtype)}"
440474

@@ -466,6 +500,8 @@ def tuple_undefined_check_cond(
466500
return self.tuple_undefined_check_cond(
467501
item_type, tuple_expr_in_c + f".f{i}", c_type_compare_val, compare
468502
)
503+
elif isinstance(item_type, RVec):
504+
return f"{tuple_expr_in_c}.f{i}.len {compare} -1"
469505
else:
470506
check = f"{tuple_expr_in_c}.f{i} {compare} {c_type_compare_val(item_type)}"
471507
if rtuple.error_overlap and check_exception:
@@ -485,6 +521,8 @@ def c_initializer_undefined_value(self, rtype: RType) -> str:
485521
return f"{{ {int_rprimitive.c_undefined} }}"
486522
items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types])
487523
return f"{{ {items} }}"
524+
elif isinstance(rtype, RVec):
525+
return "{ -1, NULL }"
488526
else:
489527
return self.c_undefined_value(rtype)
490528

@@ -518,6 +556,9 @@ def emit_inc_ref(self, dest: str, rtype: RType, *, rare: bool = False) -> None:
518556
elif isinstance(rtype, RTuple):
519557
for i, item_type in enumerate(rtype.types):
520558
self.emit_inc_ref(f"{dest}.f{i}", item_type)
559+
elif isinstance(rtype, RVec):
560+
# TODO: Only use the X variant if buf can be NULL
561+
self.emit_line(f"Py_XINCREF({dest}.buf);")
521562
elif not rtype.is_unboxed:
522563
# Always inline, since this is a simple but very hot op
523564
if rtype.may_be_immortal or not HAVE_IMMORTAL:
@@ -546,6 +587,12 @@ def emit_dec_ref(
546587
elif isinstance(rtype, RTuple):
547588
for i, item_type in enumerate(rtype.types):
548589
self.emit_dec_ref(f"{dest}.f{i}", item_type, is_xdec=is_xdec, rare=rare)
590+
elif isinstance(rtype, RVec):
591+
# TODO: Only use the X variant if buf can be NULL
592+
if rare:
593+
self.emit_line(f"CPy_XDecRef({dest}.buf);")
594+
else:
595+
self.emit_line(f"CPy_XDECREF({dest}.buf);")
549596
elif not rtype.is_unboxed:
550597
if rare:
551598
self.emit_line(f"CPy_{x}DecRef({dest});")
@@ -555,6 +602,8 @@ def emit_dec_ref(
555602
self.emit_line(f"CPy_{x}DECREF({dest});")
556603
else:
557604
self.emit_line(f"CPy_{x}DECREF_NO_IMM({dest});")
605+
elif rtype.is_refcounted:
606+
assert False, f"dec_ref not implemented for {rtype}"
558607
# Otherwise assume it's an unboxed, pointerless value and do nothing.
559608

560609
def pretty_name(self, typ: RType) -> str:
@@ -751,6 +800,40 @@ def emit_cast(
751800
elif isinstance(typ, RTuple):
752801
assert not optional
753802
self.emit_tuple_cast(src, dest, typ, declare_dest, error, src_type)
803+
elif isinstance(typ, RVec):
804+
if declare_dest:
805+
self.emit_line(f"PyObject *{dest};")
806+
# Build type check expression based on vec kind
807+
api_name = vec_api_by_item_type.get(typ.item_type)
808+
depth = typ.depth()
809+
if api_name:
810+
# Specialized vec types (vec[i64], vec[i32], etc.)
811+
check = f"(Py_TYPE({src}) == {api_name}.boxed_type)"
812+
elif depth == 0:
813+
# Generic vec types (vec[T], vec[T | None]) with reference type items
814+
item_type_c = self.vec_item_type_c(typ)
815+
check = (
816+
f"(Py_TYPE({src}) == VecTApi.boxed_type && "
817+
f"((VecTObject *){src})->vec.buf->item_type == {item_type_c})"
818+
)
819+
else:
820+
# Nested vec types (vec[vec[...]]). Check boxed type, item type, and depth.
821+
unwrapped = typ.unwrap_item_type()
822+
if unwrapped in vec_item_type_tags:
823+
type_value = str(vec_item_type_tags[unwrapped])
824+
else:
825+
type_value = self.vec_item_type_c(typ)
826+
check = (
827+
f"(Py_TYPE({src}) == VecNestedApi.boxed_type && "
828+
f"((VecNestedObject *){src})->vec.buf->item_type == {type_value} && "
829+
f"((VecNestedObject *){src})->vec.buf->depth == {depth})"
830+
)
831+
if likely:
832+
check = f"(likely{check})"
833+
self.emit_arg_check(src, dest, typ, check, optional)
834+
self.emit_lines(f" {dest} = {src};", "else {")
835+
self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
836+
self.emit_line("}")
754837
else:
755838
assert False, "Cast not implemented: %s" % typ
756839

@@ -894,6 +977,7 @@ def emit_unbox(
894977
declare_dest: If True, also declare the variable 'dest'
895978
error: What happens on error
896979
raise_exception: If True, also raise TypeError on failure
980+
optional: If True, NULL src value is allowed and will map to error value
897981
borrow: If True, create a borrowed reference
898982
899983
"""
@@ -1025,10 +1109,56 @@ def emit_unbox(
10251109
self.emit_line("}")
10261110
if optional:
10271111
self.emit_line("}")
1112+
elif isinstance(typ, RVec):
1113+
if declare_dest:
1114+
self.emit_line(f"{self.ctype(typ)} {dest};")
1115+
1116+
if optional:
1117+
self.emit_line(f"if ({src} == NULL) {{")
1118+
self.emit_line(f"{dest} = {self.c_error_value(typ)};")
1119+
self.emit_line("} else {")
1120+
1121+
specialized_api_name = vec_api_by_item_type.get(typ.item_type)
1122+
if specialized_api_name is not None:
1123+
self.emit_line(f"{dest} = {specialized_api_name}.unbox({src});")
1124+
else:
1125+
depth = typ.depth()
1126+
unwrapped = typ.unwrap_item_type()
1127+
if unwrapped in vec_item_type_tags:
1128+
type_value = str(vec_item_type_tags[unwrapped])
1129+
else:
1130+
type_value = self.vec_item_type_c(typ)
1131+
if depth == 0:
1132+
self.emit_line(f"{dest} = VecTApi.unbox({src}, {type_value});")
1133+
else:
1134+
self.emit_line(f"{dest} = VecNestedApi.unbox({src}, {type_value}, {depth});")
10281135

1136+
self.emit_line(f"if (VEC_IS_ERROR({dest})) {{")
1137+
self.emit_line(failure)
1138+
self.emit_line("}")
1139+
1140+
if optional:
1141+
self.emit_line("}")
10291142
else:
10301143
assert False, "Unboxing not implemented: %s" % typ
10311144

1145+
def vec_item_type_c(self, typ: RVec) -> str:
1146+
item_type = typ.unwrap_item_type()
1147+
type_c_ptr = self.type_c_ptr(item_type)
1148+
# Can never be None, since we unwrapped the item type above
1149+
assert type_c_ptr is not None
1150+
type_value = f"(size_t){type_c_ptr}"
1151+
if typ.is_optional():
1152+
type_value = f"({type_value} | 1)"
1153+
return type_value
1154+
1155+
def type_c_ptr(self, typ: RPrimitive | RInstance) -> str | None:
1156+
if isinstance(typ, RPrimitive) and typ.is_refcounted:
1157+
return "&" + builtin_names[typ.name][1]
1158+
elif isinstance(typ, RInstance):
1159+
return self.type_struct_name(typ.class_ir)
1160+
return None
1161+
10321162
def emit_box(
10331163
self, src: str, dest: str, typ: RType, declare_dest: bool = False, can_borrow: bool = False
10341164
) -> None:
@@ -1083,6 +1213,20 @@ def emit_box(
10831213
inner_name = self.temp_name()
10841214
self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
10851215
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
1216+
elif isinstance(typ, RVec):
1217+
specialized_api_name = vec_api_by_item_type.get(typ.item_type)
1218+
if specialized_api_name is not None:
1219+
api = specialized_api_name
1220+
elif typ.depth() > 0:
1221+
api = "VecNestedApi"
1222+
else:
1223+
api = "VecTApi"
1224+
# Empty vecs of this sort don't describe item type, so it needs to be
1225+
# passed explicitly.
1226+
item_type = self.vec_item_type_c(typ)
1227+
self.emit_line(f"{declaration}{dest} = {api}.box({src}, {item_type});")
1228+
return
1229+
self.emit_line(f"{declaration}{dest} = {api}.box({src});")
10861230
else:
10871231
assert not typ.is_unboxed
10881232
# Type is boxed -- trivially just assign.
@@ -1096,6 +1240,8 @@ def emit_error_check(self, value: str, rtype: RType, failure: str) -> None:
10961240
else:
10971241
cond = self.tuple_undefined_check_cond(rtype, value, self.c_error_value, "==")
10981242
self.emit_line(f"if ({cond}) {{")
1243+
elif isinstance(rtype, RVec):
1244+
self.emit_line(f"if ({value}.len < 0) {{")
10991245
elif rtype.error_overlap:
11001246
# The error value is also valid as a normal value, so we need to also check
11011247
# for a raised exception.
@@ -1120,6 +1266,8 @@ def emit_gc_visit(self, target: str, rtype: RType) -> None:
11201266
elif isinstance(rtype, RTuple):
11211267
for i, item_type in enumerate(rtype.types):
11221268
self.emit_gc_visit(f"{target}.f{i}", item_type)
1269+
elif isinstance(rtype, RVec):
1270+
self.emit_line(f"Py_VISIT({target}.buf);")
11231271
elif self.ctype(rtype) == "PyObject *":
11241272
# The simplest case.
11251273
self.emit_line(f"Py_VISIT({target});")
@@ -1144,6 +1292,8 @@ def emit_gc_clear(self, target: str, rtype: RType) -> None:
11441292
elif isinstance(rtype, RTuple):
11451293
for i, item_type in enumerate(rtype.types):
11461294
self.emit_gc_clear(f"{target}.f{i}", item_type)
1295+
elif isinstance(rtype, RVec):
1296+
self.emit_line(f"Py_CLEAR({target}.buf);")
11471297
elif self.ctype(rtype) == "PyObject *" and self.c_undefined_value(rtype) == "NULL":
11481298
# The simplest case.
11491299
self.emit_line(f"Py_CLEAR({target});")

mypyc/codegen/emitclass.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ def generate_setup_for_class(
653653
# We don't need to set this field to NULL since tp_alloc() already
654654
# zero-initializes `self`.
655655
if value != "NULL":
656-
emitter.emit_line(rf"self->{emitter.attr(attr)} = {value};")
656+
emitter.set_undefined_value(f"self->{emitter.attr(attr)}", rtype)
657657

658658
# Initialize attributes to default values, if necessary
659659
if defaults_fn is not None:
@@ -1193,10 +1193,11 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N
11931193
emitter.emit_attr_bitmap_set("tmp", "self", rtype, cl, attr)
11941194

11951195
if deletable:
1196-
emitter.emit_line("} else")
1197-
emitter.emit_line(f" self->{attr_field} = {emitter.c_undefined_value(rtype)};")
1196+
emitter.emit_line("} else {")
1197+
emitter.set_undefined_value(f"self->{attr_field}", rtype)
11981198
if rtype.error_overlap:
11991199
emitter.emit_attr_bitmap_clear("self", rtype, cl, attr)
1200+
emitter.emit_line("}")
12001201
emitter.emit_line("return 0;")
12011202
emitter.emit_line("}")
12021203

0 commit comments

Comments
 (0)