Skip to content

Commit 67ce16a

Browse files
committed
Value types are working for basic class definitions
1 parent c888eb9 commit 67ce16a

22 files changed

+241
-118
lines changed

mypyc/analysis/attrdefined.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def foo(self) -> int:
9090
SetMem,
9191
Unreachable,
9292
)
93-
from mypyc.ir.rtypes import RInstance
93+
from mypyc.ir.rtypes import RInstance, RInstanceValue
9494

9595
# If True, print out all always-defined attributes of native classes (to aid
9696
# debugging and testing)

mypyc/codegen/emit.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
is_tuple_rprimitive,
5050
is_uint8_rprimitive,
5151
object_rprimitive,
52-
optional_value_type,
52+
optional_value_type, RInstanceValue,
5353
)
5454
from mypyc.namegen import NameGenerator, exported_name
5555
from mypyc.sametype import is_same_type
@@ -322,6 +322,8 @@ def c_undefined_value(self, rtype: RType) -> str:
322322
return rtype.c_undefined
323323
elif isinstance(rtype, RTuple):
324324
return self.tuple_undefined_value(rtype)
325+
elif isinstance(rtype, RInstanceValue):
326+
return self.rinstance_value_undefined_value(rtype)
325327
assert False, rtype
326328

327329
def c_error_value(self, rtype: RType) -> str:
@@ -427,6 +429,8 @@ def error_value_check(self, rtype: RType, value: str, compare: str) -> str:
427429
return self.tuple_undefined_check_cond(
428430
rtype, value, self.c_error_value, compare, check_exception=False
429431
)
432+
elif isinstance(rtype, RInstanceValue):
433+
return f"{value}.vtable {compare} NULL"
430434
else:
431435
return f"{value} {compare} {self.c_error_value(rtype)}"
432436

@@ -468,6 +472,10 @@ def tuple_undefined_value(self, rtuple: RTuple) -> str:
468472
"""Undefined tuple value suitable in an expression."""
469473
return f"({rtuple.struct_name}) {self.c_initializer_undefined_value(rtuple)}"
470474

475+
def rinstance_value_undefined_value(self, rinstance_value: RInstanceValue) -> str:
476+
"""Undefined value for an unboxed instance."""
477+
return f"(({rinstance_value.struct_name(self.names)}){self.c_initializer_undefined_value(rinstance_value)})"
478+
471479
def c_initializer_undefined_value(self, rtype: RType) -> str:
472480
"""Undefined value represented in a form suitable for variable initialization."""
473481
if isinstance(rtype, RTuple):
@@ -477,6 +485,8 @@ def c_initializer_undefined_value(self, rtype: RType) -> str:
477485
return f"{{ {int_rprimitive.c_undefined} }}"
478486
items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types])
479487
return f"{{ {items} }}"
488+
elif isinstance(rtype, RInstanceValue):
489+
return "{0}"
480490
else:
481491
return self.c_undefined_value(rtype)
482492

@@ -987,7 +997,17 @@ def emit_unbox(
987997
self.emit_line("}")
988998
if optional:
989999
self.emit_line("}")
1000+
elif isinstance(typ, RInstanceValue):
1001+
if declare_dest:
1002+
self.emit_line(f"{self.ctype(typ)} {dest};")
1003+
if optional:
1004+
self.emit_line(f"if ({src} == NULL) {{")
1005+
self.emit_line(f"{dest} = {self.c_error_value(typ)};")
1006+
self.emit_line("} else {")
9901007

1008+
self.emit_line(f"{dest} = *({self.ctype(typ)} *){src};")
1009+
if optional:
1010+
self.emit_line("}")
9911011
else:
9921012
assert False, "Unboxing not implemented: %s" % typ
9931013

@@ -1041,6 +1061,22 @@ def emit_box(
10411061
inner_name = self.temp_name()
10421062
self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
10431063
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
1064+
elif isinstance(typ, RInstanceValue):
1065+
cl = typ.class_ir
1066+
generate_full = not cl.is_trait and not cl.builtin_base
1067+
assert generate_full, "Only full classes can be boxed" # only those have setup method
1068+
name_prefix = cl.name_prefix(self.names)
1069+
setup_name = f"{name_prefix}_setup"
1070+
py_type_struct = self.type_struct_name(cl)
1071+
temp_dest = self.temp_name()
1072+
self.emit_line(f"{self.ctype_spaced(typ)}*{temp_dest} = ({self.ctype_spaced(typ)}*){setup_name}({py_type_struct});")
1073+
self.emit_line(f"if (unlikely({temp_dest} == NULL))")
1074+
self.emit_line(" CPyError_OutOfMemory();")
1075+
for attr, attr_type in cl.attributes.items():
1076+
attr_name = self.attr(attr)
1077+
self.emit_line(f"{temp_dest}->{attr_name} = {src}.{attr_name};", ann="box")
1078+
1079+
self.emit_line(f"{declaration}{dest} = (PyObject *){temp_dest};")
10441080
else:
10451081
assert not typ.is_unboxed
10461082
# Type is boxed -- trivially just assign.
@@ -1054,6 +1090,8 @@ def emit_error_check(self, value: str, rtype: RType, failure: str) -> None:
10541090
else:
10551091
cond = self.tuple_undefined_check_cond(rtype, value, self.c_error_value, "==")
10561092
self.emit_line(f"if ({cond}) {{")
1093+
elif isinstance(rtype, RInstanceValue):
1094+
self.emit_line(f"if ({value}.vtable == NULL) {{")
10571095
elif rtype.error_overlap:
10581096
# The error value is also valid as a normal value, so we need to also check
10591097
# for a raised exception.

mypyc/codegen/emitclass.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import Callable, Mapping, Tuple
66

77
from mypyc.codegen.emit import Emitter, HeaderDeclaration, ReturnHandler
8-
from mypyc.codegen.emitfunc import native_function_header, struct_type
8+
from mypyc.codegen.emitfunc import native_function_header
99
from mypyc.codegen.emitwrapper import (
1010
generate_bin_op_wrapper,
1111
generate_bool_wrapper,
@@ -21,7 +21,7 @@
2121
from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX
2222
from mypyc.ir.class_ir import ClassIR, VTableEntries
2323
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR
24-
from mypyc.ir.rtypes import RTuple, RType, object_rprimitive, RInstance
24+
from mypyc.ir.rtypes import RTuple, RType, object_rprimitive
2525
from mypyc.namegen import NameGenerator
2626
from mypyc.sametype import is_same_type
2727

@@ -294,7 +294,10 @@ def emit_line() -> None:
294294
# Declare setup method that allocates and initializes an object. type is the
295295
# type of the class being initialized, which could be another class if there
296296
# is an interpreted subclass.
297-
emitter.emit_line(f"static PyObject *{setup_name}(PyTypeObject *type);")
297+
emitter.context.declarations[setup_name] = HeaderDeclaration(
298+
f"PyObject *{setup_name}(PyTypeObject *type);", needs_export=True
299+
)
300+
298301
assert cl.ctor is not None
299302
emitter.emit_line(native_function_header(cl.ctor, emitter) + ";")
300303

@@ -397,13 +400,7 @@ def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
397400

398401
for attr, rtype in base.attributes.items():
399402
if (attr, rtype) not in seen_attrs:
400-
if isinstance(rtype, RInstance) and rtype.class_ir.is_value_type:
401-
# value types use structs directly
402-
rtype_struct = struct_type(rtype.class_ir, emitter)
403-
lines.append(f"{emitter.ctype_spaced(rtype_struct)}{emitter.attr(attr)};")
404-
else:
405-
lines.append(f"{emitter.ctype_spaced(rtype)}{emitter.attr(attr)};")
406-
403+
lines.append(f"{emitter.ctype_spaced(rtype)}{emitter.attr(attr)};")
407404
seen_attrs.add((attr, rtype))
408405

409406
if isinstance(rtype, RTuple):
@@ -562,7 +559,7 @@ def generate_setup_for_class(
562559
emitter: Emitter,
563560
) -> None:
564561
"""Generate a native function that allocates an instance of a class."""
565-
emitter.emit_line("static PyObject *")
562+
emitter.emit_line("PyObject *")
566563
emitter.emit_line(f"{func_name}(PyTypeObject *type)")
567564
emitter.emit_line("{")
568565
emitter.emit_line(f"{cl.struct_name(emitter.names)} *self;")
@@ -593,7 +590,7 @@ def generate_setup_for_class(
593590

594591
# We don't need to set this field to NULL since tp_alloc() already
595592
# zero-initializes `self`.
596-
if value != "NULL":
593+
if value not in ("NULL", "0"):
597594
emitter.emit_line(rf"self->{emitter.attr(attr)} = {value};")
598595

599596
# Initialize attributes to default values, if necessary
@@ -622,10 +619,15 @@ def generate_constructor_for_class(
622619
"""Generate a native function that allocates and initializes an instance of a class."""
623620
emitter.emit_line(f"{native_function_header(fn, emitter)}")
624621
emitter.emit_line("{")
625-
emitter.emit_line(f"PyObject *self = {setup_name}({emitter.type_struct_name(cl)});")
626-
emitter.emit_line("if (self == NULL)")
627-
emitter.emit_line(" return NULL;")
628-
args = ", ".join(["self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
622+
if cl.is_value_type:
623+
emitter.emit_line(f"{cl.struct_name(emitter.names)} self = {{0}};")
624+
emitter.emit_line(f"self.vtable = {vtable_name};")
625+
args = ", ".join(["(PyObject*)&self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
626+
else:
627+
emitter.emit_line(f"PyObject *self = {setup_name}({emitter.type_struct_name(cl)});")
628+
emitter.emit_line("if (self == NULL)")
629+
emitter.emit_line(" return NULL;")
630+
args = ", ".join(["self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
629631
if init_fn is not None:
630632
emitter.emit_line(
631633
"char res = {}{}{}({});".format(
@@ -636,17 +638,25 @@ def generate_constructor_for_class(
636638
)
637639
)
638640
emitter.emit_line("if (res == 2) {")
639-
emitter.emit_line("Py_DECREF(self);")
640-
emitter.emit_line("return NULL;")
641+
if cl.is_value_type:
642+
emitter.emit_line("self.vtable = NULL;")
643+
emitter.emit_line("return self;")
644+
else:
645+
emitter.emit_line("Py_DECREF(self);")
646+
emitter.emit_line("return NULL;")
641647
emitter.emit_line("}")
642648

643649
# If there is a nontrivial ctor that we didn't define, invoke it via tp_init
644650
elif len(fn.sig.args) > 1:
645651
emitter.emit_line(f"int res = {emitter.type_struct_name(cl)}->tp_init({args});")
646652

647653
emitter.emit_line("if (res < 0) {")
648-
emitter.emit_line("Py_DECREF(self);")
649-
emitter.emit_line("return NULL;")
654+
if cl.is_value_type:
655+
emitter.emit_line("self.vtable = NULL;")
656+
emitter.emit_line("return self;")
657+
else:
658+
emitter.emit_line("Py_DECREF(self);")
659+
emitter.emit_line("return NULL;")
650660
emitter.emit_line("}")
651661

652662
emitter.emit_line("return self;")

mypyc/codegen/emitfunc.py

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
is_pointer_rprimitive,
8484
is_tagged,
8585
PyObject,
86+
RInstanceValue,
8687
)
8788

8889

@@ -240,6 +241,8 @@ def visit_branch(self, op: Branch) -> None:
240241
cond = self.emitter.tuple_undefined_check_cond(
241242
typ, self.reg(op.value), self.c_error_value, compare
242243
)
244+
elif isinstance(typ, RInstanceValue):
245+
cond = f"{self.reg(op.value)}.vtable {compare} NULL"
243246
else:
244247
cond = f"{self.reg(op.value)} {compare} {self.c_error_value(typ)}"
245248
else:
@@ -363,7 +366,11 @@ def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> st
363366
if op.class_type.class_ir.is_trait:
364367
assert not decl_cl.is_trait
365368
cast = f"({decl_cl.struct_name(self.emitter.names)} *)"
366-
return f"({cast}{obj})->{self.emitter.attr(op.attr)}"
369+
370+
if op.obj.type.is_unboxed:
371+
return f"{obj}.{self.emitter.attr(op.attr)}"
372+
else:
373+
return f"({cast}{obj})->{self.emitter.attr(op.attr)}"
367374

368375
def visit_get_attr(self, op: GetAttr) -> None:
369376
dest = self.reg(op)
@@ -548,42 +555,8 @@ def get_dest_assign(self, dest: Value) -> str:
548555
else:
549556
return ""
550557

551-
def try_emit_new_value_type_call(self, op: Call) -> bool:
552-
if not isinstance(op.type, RInstance):
553-
return False
554-
555-
cl = op.type.class_ir
556-
if op.fn.fullname != op.type.name or not cl.is_value_type:
557-
return False
558-
559-
assert not op.type.is_refcounted, "Value types must not be refcounted"
560-
dest = self.get_dest_assign(op)
561-
struct_name = cl.struct_name(self.names)
562-
temp_name = self.emitter.temp_name()
563-
temp_name2 = self.emitter.temp_name()
564-
self.emit_line(f"{struct_name} {temp_name};")
565-
init_fn = cl.get_method("__init__")
566-
if init_fn:
567-
args = [self.reg(arg) for arg in op.args]
568-
self.emitter.emit_line(
569-
"char {} = {}{}{}({});".format(
570-
temp_name2,
571-
self.emitter.get_group_prefix(init_fn.decl),
572-
NATIVE_PREFIX,
573-
init_fn.cname(self.emitter.names),
574-
", ".join([f"&{temp_name}"] + args),
575-
)
576-
)
577-
self.emit_line(f"{dest}{temp_name2} != 2 ? (PyObject *)&{temp_name} : NULL;")
578-
else:
579-
self.emit_line(f"{dest}(PyObject *)&{temp_name};")
580-
return True
581-
582558
def visit_call(self, op: Call) -> None:
583559
"""Call native function."""
584-
if self.try_emit_new_value_type_call(op):
585-
return
586-
587560
dest = self.get_dest_assign(op)
588561
args = ", ".join(self.reg(arg) for arg in op.args)
589562
lib = self.emitter.get_group_prefix(op.fn)

mypyc/codegen/emitmodule.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from mypyc.transform.lower import lower_ir
6464
from mypyc.transform.refcount import insert_ref_count_opcodes
6565
from mypyc.transform.uninit import insert_uninit_checks
66+
from mypyc.transform.value_type_init import patch_value_type_init_methods
6667

6768
# All of the modules being compiled are divided into "groups". A group
6869
# is a set of modules that are placed into the same shared library.
@@ -231,6 +232,8 @@ def compile_scc_to_ir(
231232

232233
for module in modules.values():
233234
for fn in module.functions:
235+
# Patch init methods for Value Types
236+
patch_value_type_init_methods(fn, compiler_options)
234237
# Insert uninit checks.
235238
insert_uninit_checks(fn)
236239
# Insert exception handling.

mypyc/codegen/emitwrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
is_bool_rprimitive,
3535
is_int_rprimitive,
3636
is_object_rprimitive,
37-
object_rprimitive,
37+
object_rprimitive, RInstanceValue,
3838
)
3939
from mypyc.namegen import NameGenerator
4040

mypyc/ir/class_ir.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from mypyc.common import PROPSET_PREFIX, JsonDict
88
from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature
99
from mypyc.ir.ops import DeserMaps, Value
10-
from mypyc.ir.rtypes import RInstance, RType, deserialize_type
10+
from mypyc.ir.rtypes import RInstance, RType, deserialize_type, RInstanceValue
1111
from mypyc.namegen import NameGenerator, exported_name
1212

1313
# Some notes on the vtable layout: Each concrete class has a vtable
@@ -116,6 +116,8 @@ def __init__(
116116
# Does this class need getseters to be generated for its attributes? (getseters are also
117117
# added if is_generated is False)
118118
self.needs_getseters = False
119+
# A value type is a class that can be passed by value instead of by reference.
120+
self.is_value_type = self.is_ext_class and self.is_immutable and not self.has_dict
119121
# Is this class declared as serializable (supports copy.copy
120122
# and pickle) using @mypyc_attr(serializable=True)?
121123
#
@@ -133,8 +135,10 @@ def __init__(
133135
# of the object for that class. We currently only support this
134136
# in a few ad-hoc cases.
135137
self.builtin_base: str | None = None
138+
# The RType for instances of this class
139+
self.rtype = RInstanceValue(self) if self.is_value_type else RInstance(self)
136140
# Default empty constructor
137-
self.ctor = FuncDecl(name, None, module_name, FuncSignature([], RInstance(self)))
141+
self.ctor = FuncDecl(name, None, module_name, FuncSignature([], self.rtype))
138142
# Attributes defined in the class (not inherited)
139143
self.attributes: dict[str, RType] = {}
140144
# Deletable attributes
@@ -212,10 +216,6 @@ def __repr__(self) -> str:
212216
def fullname(self) -> str:
213217
return f"{self.module_name}.{self.name}"
214218

215-
@property
216-
def is_value_type(self) -> bool:
217-
return self.is_ext_class and self.is_immutable and not self.has_dict
218-
219219
def real_base(self) -> ClassIR | None:
220220
"""Return the actual concrete base class, if there is one."""
221221
if len(self.mro) > 1 and not self.mro[1].is_trait:
@@ -289,6 +289,12 @@ def name_prefix(self, names: NameGenerator) -> str:
289289
def struct_name(self, names: NameGenerator) -> str:
290290
return f"{exported_name(self.fullname)}Object"
291291

292+
def struct_name2(self) -> str:
293+
return f"{exported_name(self.fullname)}Object"
294+
295+
def struct_data_name(self) -> str:
296+
return f"{exported_name(self.fullname)}Data"
297+
292298
def get_method_and_class(
293299
self, name: str, *, prefer_method: bool = False
294300
) -> tuple[FuncIR, ClassIR] | None:

mypyc/ir/ops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
object_rprimitive,
3636
pointer_rprimitive,
3737
short_int_rprimitive,
38-
void_rtype,
38+
void_rtype, RInstanceValue,
3939
)
4040

4141
if TYPE_CHECKING:

0 commit comments

Comments
 (0)