Skip to content

Commit dde01d6

Browse files
authored
[mypyc] Support tuples of native ints (#14252)
A tuple such as `tuple[i64, i64]` can't have a dedicated error value, so use overlapping error values, similarly to how we support plain native integers such as `i64`. For a heterogeneous tuple such as as `tuple[i64, str]` we attempt to store the error value in the smallest item index where the value type supports error values (1 in this example). This affects error returns, undefined attributes, default arguments and undefined locals.
1 parent 7e9aa74 commit dde01d6

File tree

14 files changed

+173
-48
lines changed

14 files changed

+173
-48
lines changed

mypyc/analysis/attrdefined.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def foo(self) -> int:
9191
SetMem,
9292
Unreachable,
9393
)
94-
from mypyc.ir.rtypes import RInstance, is_fixed_width_rtype
94+
from mypyc.ir.rtypes import RInstance
9595

9696
# If True, print out all always-defined attributes of native classes (to aid
9797
# debugging and testing)
@@ -424,5 +424,5 @@ def detect_undefined_bitmap(cl: ClassIR, seen: Set[ClassIR]) -> None:
424424
if len(cl.base_mro) > 1:
425425
cl.bitmap_attrs.extend(cl.base_mro[1].bitmap_attrs)
426426
for n, t in cl.attributes.items():
427-
if is_fixed_width_rtype(t) and not cl.is_always_defined(n):
427+
if t.error_overlap and not cl.is_always_defined(n):
428428
cl.bitmap_attrs.append(n)

mypyc/codegen/emit.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ def _emit_attr_bitmap_update(
364364
self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str, clear: bool
365365
) -> None:
366366
if value:
367-
self.emit_line(f"if (unlikely({value} == {self.c_undefined_value(rtype)})) {{")
367+
check = self.error_value_check(rtype, value, "==")
368+
self.emit_line(f"if (unlikely({check})) {{")
368369
index = cl.bitmap_attrs.index(attr)
369370
mask = 1 << (index & (BITMAP_BITS - 1))
370371
bitmap = self.attr_bitmap_expr(obj, cl, index)
@@ -389,42 +390,58 @@ def emit_undefined_attr_check(
389390
*,
390391
unlikely: bool = False,
391392
) -> None:
392-
if isinstance(rtype, RTuple):
393-
check = "{}".format(
394-
self.tuple_undefined_check_cond(rtype, attr_expr, self.c_undefined_value, compare)
395-
)
396-
else:
397-
undefined = self.c_undefined_value(rtype)
398-
check = f"{attr_expr} {compare} {undefined}"
393+
check = self.error_value_check(rtype, attr_expr, compare)
399394
if unlikely:
400395
check = f"unlikely({check})"
401-
if is_fixed_width_rtype(rtype):
396+
if rtype.error_overlap:
402397
index = cl.bitmap_attrs.index(attr)
403398
bit = 1 << (index & (BITMAP_BITS - 1))
404399
attr = self.bitmap_field(index)
405400
obj_expr = f"({cl.struct_name(self.names)} *){obj}"
406401
check = f"{check} && !(({obj_expr})->{attr} & {bit})"
407402
self.emit_line(f"if ({check}) {{")
408403

404+
def error_value_check(self, rtype: RType, value: str, compare: str) -> str:
405+
if isinstance(rtype, RTuple):
406+
return self.tuple_undefined_check_cond(
407+
rtype, value, self.c_error_value, compare, check_exception=False
408+
)
409+
else:
410+
return f"{value} {compare} {self.c_error_value(rtype)}"
411+
409412
def tuple_undefined_check_cond(
410413
self,
411414
rtuple: RTuple,
412415
tuple_expr_in_c: str,
413416
c_type_compare_val: Callable[[RType], str],
414417
compare: str,
418+
*,
419+
check_exception: bool = True,
415420
) -> str:
416421
if len(rtuple.types) == 0:
417422
# empty tuple
418423
return "{}.empty_struct_error_flag {} {}".format(
419424
tuple_expr_in_c, compare, c_type_compare_val(int_rprimitive)
420425
)
421-
item_type = rtuple.types[0]
426+
if rtuple.error_overlap:
427+
i = 0
428+
item_type = rtuple.types[0]
429+
else:
430+
for i, typ in enumerate(rtuple.types):
431+
if not typ.error_overlap:
432+
item_type = rtuple.types[i]
433+
break
434+
else:
435+
assert False, "not expecting tuple with error overlap"
422436
if isinstance(item_type, RTuple):
423437
return self.tuple_undefined_check_cond(
424-
item_type, tuple_expr_in_c + ".f0", c_type_compare_val, compare
438+
item_type, tuple_expr_in_c + f".f{i}", c_type_compare_val, compare
425439
)
426440
else:
427-
return f"{tuple_expr_in_c}.f0 {compare} {c_type_compare_val(item_type)}"
441+
check = f"{tuple_expr_in_c}.f{i} {compare} {c_type_compare_val(item_type)}"
442+
if rtuple.error_overlap and check_exception:
443+
check += " && PyErr_Occurred()"
444+
return check
428445

429446
def tuple_undefined_value(self, rtuple: RTuple) -> str:
430447
return "tuple_undefined_" + rtuple.unique_id
@@ -986,18 +1003,18 @@ def emit_box(
9861003

9871004
def emit_error_check(self, value: str, rtype: RType, failure: str) -> None:
9881005
"""Emit code for checking a native function return value for uncaught exception."""
989-
if is_fixed_width_rtype(rtype):
990-
# The error value is also valid as a normal value, so we need to also check
991-
# for a raised exception.
992-
self.emit_line(f"if ({value} == {self.c_error_value(rtype)} && PyErr_Occurred()) {{")
993-
elif not isinstance(rtype, RTuple):
994-
self.emit_line(f"if ({value} == {self.c_error_value(rtype)}) {{")
995-
else:
1006+
if isinstance(rtype, RTuple):
9961007
if len(rtype.types) == 0:
9971008
return # empty tuples can't fail.
9981009
else:
9991010
cond = self.tuple_undefined_check_cond(rtype, value, self.c_error_value, "==")
10001011
self.emit_line(f"if ({cond}) {{")
1012+
elif rtype.error_overlap:
1013+
# The error value is also valid as a normal value, so we need to also check
1014+
# for a raised exception.
1015+
self.emit_line(f"if ({value} == {self.c_error_value(rtype)} && PyErr_Occurred()) {{")
1016+
else:
1017+
self.emit_line(f"if ({value} == {self.c_error_value(rtype)}) {{")
10011018
self.emit_lines(failure, "}")
10021019

10031020
def emit_gc_visit(self, target: str, rtype: RType) -> None:

mypyc/codegen/emitclass.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX, use_fastcall
2121
from mypyc.ir.class_ir import ClassIR, VTableEntries
2222
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR
23-
from mypyc.ir.rtypes import RTuple, RType, is_fixed_width_rtype, object_rprimitive
23+
from mypyc.ir.rtypes import RTuple, RType, object_rprimitive
2424
from mypyc.namegen import NameGenerator
2525
from mypyc.sametype import is_same_type
2626

@@ -960,13 +960,13 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N
960960
emitter.emit_lines("if (!tmp)", " return -1;")
961961
emitter.emit_inc_ref("tmp", rtype)
962962
emitter.emit_line(f"self->{attr_field} = tmp;")
963-
if is_fixed_width_rtype(rtype) and not always_defined:
963+
if rtype.error_overlap and not always_defined:
964964
emitter.emit_attr_bitmap_set("tmp", "self", rtype, cl, attr)
965965

966966
if deletable:
967967
emitter.emit_line("} else")
968968
emitter.emit_line(f" self->{attr_field} = {emitter.c_undefined_value(rtype)};")
969-
if is_fixed_width_rtype(rtype):
969+
if rtype.error_overlap:
970970
emitter.emit_attr_bitmap_clear("self", rtype, cl, attr)
971971
emitter.emit_line("return 0;")
972972
emitter.emit_line("}")

mypyc/codegen/emitfunc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
RStruct,
6161
RTuple,
6262
RType,
63-
is_fixed_width_rtype,
6463
is_int32_rprimitive,
6564
is_int64_rprimitive,
6665
is_int_rprimitive,
@@ -442,7 +441,7 @@ def visit_set_attr(self, op: SetAttr) -> None:
442441
self.emitter.emit_dec_ref(attr_expr, attr_rtype)
443442
if not always_defined:
444443
self.emitter.emit_line("}")
445-
elif is_fixed_width_rtype(attr_rtype) and not cl.is_always_defined(op.attr):
444+
elif attr_rtype.error_overlap and not cl.is_always_defined(op.attr):
446445
# If there is overlap with the error value, update bitmap to mark
447446
# attribute as defined.
448447
self.emitter.emit_attr_bitmap_set(src, obj, attr_rtype, cl, op.attr)

mypyc/codegen/emitwrapper.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
RInstance,
3333
RType,
3434
is_bool_rprimitive,
35-
is_fixed_width_rtype,
3635
is_int_rprimitive,
3736
is_object_rprimitive,
3837
object_rprimitive,
@@ -718,9 +717,10 @@ def generate_arg_check(
718717
"""
719718
error = error or AssignHandler()
720719
if typ.is_unboxed:
721-
if is_fixed_width_rtype(typ) and optional:
720+
if typ.error_overlap and optional:
722721
# Update bitmap is value is provided.
723-
emitter.emit_line(f"{emitter.ctype(typ)} arg_{name} = 0;")
722+
init = emitter.c_undefined_value(typ)
723+
emitter.emit_line(f"{emitter.ctype(typ)} arg_{name} = {init};")
724724
emitter.emit_line(f"if (obj_{name} != NULL) {{")
725725
bitmap = bitmap_name(bitmap_arg_index // BITMAP_BITS)
726726
emitter.emit_line(f"{bitmap} |= 1 << {bitmap_arg_index & (BITMAP_BITS - 1)};")
@@ -835,7 +835,7 @@ def emit_arg_processing(
835835
optional=optional,
836836
bitmap_arg_index=bitmap_arg_index,
837837
)
838-
if optional and is_fixed_width_rtype(typ):
838+
if optional and typ.error_overlap:
839839
bitmap_arg_index += 1
840840

841841
def emit_call(self, not_implemented_handler: str = "") -> None:

mypyc/ir/func_ir.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
Register,
1818
Value,
1919
)
20-
from mypyc.ir.rtypes import RType, bitmap_rprimitive, deserialize_type, is_fixed_width_rtype
20+
from mypyc.ir.rtypes import RType, bitmap_rprimitive, deserialize_type
2121
from mypyc.namegen import NameGenerator
2222

2323

@@ -113,7 +113,7 @@ def deserialize(cls, data: JsonDict, ctx: DeserMaps) -> FuncSignature:
113113
def num_bitmap_args(args: tuple[RuntimeArg, ...]) -> int:
114114
n = 0
115115
for arg in args:
116-
if is_fixed_width_rtype(arg.type) and arg.kind.is_optional():
116+
if arg.type.error_overlap and arg.kind.is_optional():
117117
n += 1
118118
return (n + (BITMAP_BITS - 1)) // BITMAP_BITS
119119

mypyc/ir/ops.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
int_rprimitive,
2929
is_bit_rprimitive,
3030
is_bool_rprimitive,
31-
is_fixed_width_rtype,
3231
is_int_rprimitive,
3332
is_none_rprimitive,
3433
is_pointer_rprimitive,
@@ -632,7 +631,7 @@ def __init__(self, obj: Value, attr: str, line: int, *, borrow: bool = False) ->
632631
self.class_type = obj.type
633632
attr_type = obj.type.attr_type(attr)
634633
self.type = attr_type
635-
if is_fixed_width_rtype(attr_type):
634+
if attr_type.error_overlap:
636635
self.error_kind = ERR_MAGIC_OVERLAPPING
637636
self.is_borrowed = borrow and attr_type.is_refcounted
638637

@@ -785,7 +784,7 @@ class TupleGet(RegisterOp):
785784

786785
error_kind = ERR_NEVER
787786

788-
def __init__(self, src: Value, index: int, line: int) -> None:
787+
def __init__(self, src: Value, index: int, line: int = -1) -> None:
789788
super().__init__(line)
790789
self.src = src
791790
self.index = index

mypyc/ir/rtypes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,7 @@ def __init__(self, types: list[RType]) -> None:
572572
# Nominally the max c length is 31 chars, but I'm not honestly worried about this.
573573
self.struct_name = f"tuple_{self.unique_id}"
574574
self._ctype = f"{self.struct_name}"
575+
self.error_overlap = all(t.error_overlap for t in self.types) and bool(self.types)
575576

576577
def accept(self, visitor: RTypeVisitor[T]) -> T:
577578
return visitor.visit_rtuple(self)

mypyc/irbuild/builder.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@
9090
c_pyssize_t_rprimitive,
9191
dict_rprimitive,
9292
int_rprimitive,
93-
is_fixed_width_rtype,
9493
is_list_rprimitive,
9594
is_none_rprimitive,
9695
is_object_rprimitive,
@@ -1308,7 +1307,7 @@ def get_default() -> Value:
13081307

13091308
assert isinstance(target, AssignmentTargetRegister)
13101309
reg = target.register
1311-
if not is_fixed_width_rtype(reg.type):
1310+
if not reg.type.error_overlap:
13121311
builder.assign_if_null(target.register, get_default, arg.initializer.line)
13131312
else:
13141313
builder.assign_if_bitmap_unset(

mypyc/irbuild/env_class.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def g() -> int:
2121
from mypyc.common import BITMAP_BITS, ENV_ATTR_NAME, SELF_NAME, bitmap_name
2222
from mypyc.ir.class_ir import ClassIR
2323
from mypyc.ir.ops import Call, GetAttr, SetAttr, Value
24-
from mypyc.ir.rtypes import RInstance, bitmap_rprimitive, is_fixed_width_rtype, object_rprimitive
24+
from mypyc.ir.rtypes import RInstance, bitmap_rprimitive, object_rprimitive
2525
from mypyc.irbuild.builder import IRBuilder, SymbolTarget
2626
from mypyc.irbuild.context import FuncInfo, GeneratorClass, ImplicitClass
2727
from mypyc.irbuild.targets import AssignmentTargetAttr
@@ -163,7 +163,7 @@ def num_bitmap_args(builder: IRBuilder, args: list[Argument]) -> int:
163163
n = 0
164164
for arg in args:
165165
t = builder.type_to_rtype(arg.variable.type)
166-
if is_fixed_width_rtype(t) and arg.kind.is_optional():
166+
if t.error_overlap and arg.kind.is_optional():
167167
n += 1
168168
return (n + (BITMAP_BITS - 1)) // BITMAP_BITS
169169

0 commit comments

Comments
 (0)