Skip to content

Commit

Permalink
Value types tests working
Browse files Browse the repository at this point in the history
  • Loading branch information
jairov4 committed Oct 17, 2024
1 parent 6868d34 commit 51379e2
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 104 deletions.
38 changes: 24 additions & 14 deletions mypyc/codegen/emit.py
Original file line number Diff line number Diff line change
Expand Up @@ -361,14 +361,22 @@ def bitmap_field(self, index: int) -> str:
return "bitmap"
return f"bitmap{n + 1}"

def attr_bitmap_expr(self, obj: str, cl: ClassIR, index: int) -> str:
"""Return reference to the attribute definedness bitmap."""
cast = f"({cl.struct_name(self.names)} *)"
def attr_bitmap_expr(self, obj: str, cl: RInstance, index: int) -> str:
"""
Return reference to the attribute definedness bitmap.
If a_ref is True, assume object's type is a reference.
Otherwise, the object type is indicated by the class IR.
"""
attr = self.bitmap_field(index)
return f"({cast}{obj})->{attr}"
if cl.is_unboxed:
return f"{obj}.{attr}"
else:
cast = f"({cl.struct_name(self.names)} *)"
return f"({cast}{obj})->{attr}"

def emit_attr_bitmap_set(
self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str
self, value: str, obj: str, rtype: RType, cl: RInstance, attr: str
) -> None:
"""Mark an attribute as defined in the attribute bitmap.
Expand All @@ -377,20 +385,20 @@ def emit_attr_bitmap_set(
"""
self._emit_attr_bitmap_update(value, obj, rtype, cl, attr, clear=False)

def emit_attr_bitmap_clear(self, obj: str, rtype: RType, cl: ClassIR, attr: str) -> None:
def emit_attr_bitmap_clear(self, obj: str, rtype: RType, cl: RInstance, attr: str) -> None:
"""Mark an attribute as undefined in the attribute bitmap.
Unlike emit_attr_bitmap_set, clear unconditionally.
"""
self._emit_attr_bitmap_update("", obj, rtype, cl, attr, clear=True)

def _emit_attr_bitmap_update(
self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str, clear: bool
self, value: str, obj: str, rtype: RType, cl: RInstance, attr: str, clear: bool
) -> None:
if value:
check = self.error_value_check(rtype, value, "==")
self.emit_line(f"if (unlikely({check})) {{")
index = cl.bitmap_attrs.index(attr)
index = cl.class_ir.bitmap_attrs.index(attr)
mask = 1 << (index & (BITMAP_BITS - 1))
bitmap = self.attr_bitmap_expr(obj, cl, index)
if clear:
Expand All @@ -410,19 +418,19 @@ def emit_undefined_attr_check(
compare: str,
obj: str,
attr: str,
cl: ClassIR,
cl: RInstance,
*,
unlikely: bool = False,
) -> None:
check = self.error_value_check(rtype, attr_expr, compare)
if unlikely:
check = f"unlikely({check})"
if rtype.error_overlap:
index = cl.bitmap_attrs.index(attr)
index = cl.class_ir.bitmap_attrs.index(attr)
attr_expr = self.attr_bitmap_expr(obj, cl, index)
bit = 1 << (index & (BITMAP_BITS - 1))
attr = self.bitmap_field(index)
obj_expr = f"({cl.struct_name(self.names)} *){obj}"
check = f"{check} && !(({obj_expr})->{attr} & {bit})"
check = f"{check} && !({attr_expr} & {bit})"

self.emit_line(f"if ({check}) {{")

def error_value_check(self, rtype: RType, value: str, compare: str) -> str:
Expand Down Expand Up @@ -500,6 +508,8 @@ def declare_tuple_struct(self, tuple_type: RTuple) -> None:
# XXX other types might eventually need similar behavior
if isinstance(typ, RTuple):
dependencies.add(typ.struct_name)
if isinstance(typ, RInstanceValue):
dependencies.add(typ.struct_name(self.names))

self.context.declarations[tuple_type.struct_name] = HeaderDeclaration(
self.tuple_c_declaration(tuple_type), dependencies=dependencies, is_type=True
Expand Down Expand Up @@ -1075,7 +1085,7 @@ def emit_box(
)
self.emit_line(f"if (unlikely({temp_dest} == NULL))")
self.emit_line(" CPyError_OutOfMemory();")
for attr, attr_type in cl.attributes.items():
for attr, attr_type in cl.all_attributes().items():
attr_name = self.attr(attr)
self.emit_line(f"{temp_dest}->{attr_name} = {src}.{attr_name};", ann="box")

Expand Down
37 changes: 24 additions & 13 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX
from mypyc.ir.class_ir import ClassIR, VTableEntries
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR
from mypyc.ir.rtypes import RTuple, RType, object_rprimitive
from mypyc.ir.rtypes import RTuple, RType, object_rprimitive, RInstanceValue, RInstance
from mypyc.namegen import NameGenerator
from mypyc.sametype import is_same_type

Expand Down Expand Up @@ -294,9 +294,14 @@ def emit_line() -> None:
# Declare setup method that allocates and initializes an object. type is the
# type of the class being initialized, which could be another class if there
# is an interpreted subclass.
emitter.context.declarations[setup_name] = HeaderDeclaration(
f"PyObject *{setup_name}(PyTypeObject *type);", needs_export=True
)
if cl.is_value_type:
# Value types will need this method be exported because it will be required
# when boxing the value type instance.
emitter.context.declarations[setup_name] = HeaderDeclaration(
f"PyObject *{setup_name}(PyTypeObject *type);", needs_export=True
)
else:
emitter.emit_line(f"static PyObject *{setup_name}(PyTypeObject *type);")

assert cl.ctor is not None
emitter.emit_line(native_function_header(cl.ctor, emitter) + ";")
Expand Down Expand Up @@ -949,11 +954,13 @@ def generate_getter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N
always_defined = cl.is_always_defined(attr) and not rtype.is_refcounted

if not always_defined:
emitter.emit_undefined_attr_check(rtype, attr_expr, "==", "self", attr, cl, unlikely=True)
clt = RInstance(cl)
emitter.emit_undefined_attr_check(rtype, attr_expr, "==", "self", attr, clt, unlikely=True)
emitter.emit_line("PyErr_SetString(PyExc_AttributeError,")
emitter.emit_line(f' "attribute {repr(attr)} of {repr(cl.name)} undefined");')
emitter.emit_line("return NULL;")
emitter.emit_line("}")

emitter.emit_inc_ref(f"self->{attr_field}", rtype)
emitter.emit_box(f"self->{attr_field}", "retval", rtype, declare_dest=True)
emitter.emit_line("return retval;")
Expand Down Expand Up @@ -988,7 +995,8 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N
if rtype.is_refcounted:
attr_expr = f"self->{attr_field}"
if not always_defined:
emitter.emit_undefined_attr_check(rtype, attr_expr, "!=", "self", attr, cl)
clt = RInstance(cl)
emitter.emit_undefined_attr_check(rtype, attr_expr, "!=", "self", attr, clt)
emitter.emit_dec_ref(f"self->{attr_field}", rtype)
if not always_defined:
emitter.emit_line("}")
Expand All @@ -1006,13 +1014,13 @@ def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> N
emitter.emit_inc_ref("tmp", rtype)
emitter.emit_line(f"self->{attr_field} = tmp;")
if rtype.error_overlap and not always_defined:
emitter.emit_attr_bitmap_set("tmp", "self", rtype, cl, attr)
emitter.emit_attr_bitmap_set("tmp", "self", rtype, RInstance(cl), attr)

if deletable:
emitter.emit_line("} else")
emitter.emit_line(f" self->{attr_field} = {emitter.c_undefined_value(rtype)};")
if rtype.error_overlap:
emitter.emit_attr_bitmap_clear("self", rtype, cl, attr)
emitter.emit_attr_bitmap_clear("self", rtype, RInstance(cl), attr)
emitter.emit_line("return 0;")
emitter.emit_line("}")

Expand All @@ -1027,19 +1035,22 @@ def generate_readonly_getter(
)
)
emitter.emit_line("{")

arg0 = func_ir.args[0].type
obj = "*self" if isinstance(arg0, RInstanceValue) else "(PyObject *)self"

if rtype.is_unboxed:
emitter.emit_line(
"{}retval = {}{}((PyObject *) self);".format(
emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names)
"{}retval = {}{}({});".format(
emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names), obj
)
)
emitter.emit_error_check("retval", rtype, "return NULL;")
emitter.emit_box("retval", "retbox", rtype, declare_dest=True)
emitter.emit_line("return retbox;")
else:
emitter.emit_line(
f"return {NATIVE_PREFIX}{func_ir.cname(emitter.names)}((PyObject *) self);"
)
emitter.emit_line(f"return {NATIVE_PREFIX}{func_ir.cname(emitter.names)}({obj});")

emitter.emit_line("}")


Expand Down
61 changes: 26 additions & 35 deletions mypyc/codegen/emitfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
TYPE_VAR_PREFIX,
)
from mypyc.ir.class_ir import ClassIR
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values
from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values, FUNC_NORMAL
from mypyc.ir.ops import (
ERR_FALSE,
NAMESPACE_MODULE,
Expand Down Expand Up @@ -71,14 +71,12 @@
)
from mypyc.ir.pprint import generate_names_for_ir
from mypyc.ir.rtypes import (
PyObject,
RArray,
RInstance,
RInstanceValue,
RStruct,
RTuple,
RType,
c_pyssize_t_rprimitive,
is_int32_rprimitive,
is_int64_rprimitive,
is_int_rprimitive,
Expand All @@ -87,18 +85,6 @@
)


def struct_type(class_ir: ClassIR, emitter: Emitter) -> RStruct:
"""Return the struct type for this instance type."""
python_fields: list[tuple[str, RType]] = [
("head", PyObject),
("vtable", c_pyssize_t_rprimitive),
]
class_fields = list(class_ir.attributes.items())
attr_names = [emitter.attr(name) for name, _ in python_fields + class_fields]
attr_types = [rtype for _, rtype in python_fields + class_fields]
return RStruct(class_ir.struct_name(emitter.names), attr_names, attr_types)


def native_function_type(fn: FuncIR, emitter: Emitter) -> str:
args = ", ".join(emitter.ctype(arg.type) for arg in fn.args) or "void"
ret = emitter.ctype(fn.ret_type)
Expand Down Expand Up @@ -295,7 +281,7 @@ def visit_assign(self, op: Assign) -> None:
# clang whines about self assignment (which we might generate
# for some casts), so don't emit it.
if dest != src:
# We sometimes assign from an integer prepresentation of a pointer
# We sometimes assign from an integer representation of a pointer
# to a real pointer, and C compilers insist on a cast.
if op.src.type.is_unboxed and not op.dest.type.is_unboxed:
src = f"(void *){src}"
Expand Down Expand Up @@ -406,20 +392,12 @@ def visit_get_attr(self, op: GetAttr) -> None:
attr_expr = self.get_attr_expr(obj, op, decl_cl)
always_defined = cl.is_always_defined(op.attr)
# This steals the reference to src, so we don't need to increment the arg
if isinstance(attr_rtype, RInstance) and attr_rtype.class_ir.is_value_type:
# special case for value types, it is unboxed in the struct
struct_name = attr_rtype.class_ir.struct_name(self.names)
temp = self.emitter.temp_name()
self.emitter.emit_line(f"{struct_name} {temp} = {attr_expr};")
self.emitter.emit_line(f"{dest} = (PyObject *)&{temp};")
always_defined = True
else:
self.emitter.emit_line(f"{dest} = {attr_expr};")
self.emitter.emit_line(f"{dest} = {attr_expr};")

merged_branch = None
if not always_defined:
self.emitter.emit_undefined_attr_check(
attr_rtype, dest, "==", obj, op.attr, cl, unlikely=True
attr_rtype, dest, "==", obj, op.attr, rtype, unlikely=True
)
branch = self.next_branch()
if branch is not None:
Expand Down Expand Up @@ -466,7 +444,8 @@ def visit_set_attr(self, op: SetAttr) -> None:
dest = self.reg(op)
obj = self.reg(op.obj)
src = self.reg(op.src)
rtype = op.class_type
rtype = op.obj.type
assert isinstance(rtype, RInstance)
cl = rtype.class_ir
attr_rtype, decl_cl = cl.attr_details(op.attr)
if cl.get_method(op.attr):
Expand Down Expand Up @@ -501,23 +480,18 @@ def visit_set_attr(self, op: SetAttr) -> None:
always_defined = cl.is_always_defined(op.attr)
if not always_defined:
self.emitter.emit_undefined_attr_check(
attr_rtype, attr_expr, "!=", obj, op.attr, cl
attr_rtype, attr_expr, "!=", obj, op.attr, rtype
)
self.emitter.emit_dec_ref(attr_expr, attr_rtype)
if not always_defined:
self.emitter.emit_line("}")
elif attr_rtype.error_overlap and not cl.is_always_defined(op.attr):
# If there is overlap with the error value, update bitmap to mark
# attribute as defined.
self.emitter.emit_attr_bitmap_set(src, obj, attr_rtype, cl, op.attr)
self.emitter.emit_attr_bitmap_set(src, obj, attr_rtype, rtype, op.attr)

# This steals the reference to src, so we don't need to increment the arg
if isinstance(attr_rtype, RInstance) and attr_rtype.class_ir.is_value_type:
# special case for value types, it is unboxed in the struct
struct_name = attr_rtype.class_ir.struct_name(self.names)
self.emitter.emit_line(f"{attr_expr} = *({struct_name} *)({src});")
else:
self.emitter.emit_line(f"{attr_expr} = {src};")
self.emitter.emit_line(f"{attr_expr} = {src};")
if op.error_kind == ERR_FALSE:
self.emitter.emit_line(f"{dest} = 1;")

Expand Down Expand Up @@ -589,6 +563,20 @@ def emit_method_call(self, dest: str, op_obj: Value, name: str, op_args: list[Va
if method.decl.kind == FUNC_STATICMETHOD
else [f"(PyObject *)Py_TYPE({obj})"] if method.decl.kind == FUNC_CLASSMETHOD else [obj]
)
need_box_obj = (
method.decl.kind == FUNC_NORMAL
and rtype.is_unboxed
and not method.args[0].type.is_unboxed
)
obj_boxed = ""
if need_box_obj:
# for cases where obj.method(...) is called and obj is unboxed, but method
# expects a boxed due inheritance or trait. e.g. obj is a value type
# but method comes from a parent which not
obj_boxed = self.temp_name()
self.emitter.emit_box(obj, obj_boxed, rtype, declare_dest=True)
obj_args = [obj_boxed]

args = ", ".join(obj_args + [self.reg(arg) for arg in op_args])
mtype = native_function_type(method, self.emitter)
version = "_TRAIT" if rtype.class_ir.is_trait else ""
Expand All @@ -613,6 +601,9 @@ def emit_method_call(self, dest: str, op_obj: Value, name: str, op_args: list[Va
)
)

if need_box_obj:
self.emitter.emit_dec_ref(obj_boxed, RInstance(rtype.class_ir))

def visit_inc_ref(self, op: IncRef) -> None:
src = self.reg(op.src)
self.emit_inc_ref(src, op.src.type)
Expand Down
Loading

0 comments on commit 51379e2

Please sign in to comment.