Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mypyc] Promotion to Value Type optimization #17932

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
77 changes: 65 additions & 12 deletions mypyc/codegen/emit.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from mypyc.ir.ops import BasicBlock, Value
from mypyc.ir.rtypes import (
RInstance,
RInstanceValue,
RPrimitive,
RTuple,
RType,
Expand Down Expand Up @@ -322,6 +323,8 @@ def c_undefined_value(self, rtype: RType) -> str:
return rtype.c_undefined
elif isinstance(rtype, RTuple):
return self.tuple_undefined_value(rtype)
elif isinstance(rtype, RInstanceValue):
return self.rinstance_value_undefined_value(rtype)
assert False, rtype

def c_error_value(self, rtype: RType) -> str:
Expand Down Expand Up @@ -358,14 +361,17 @@ 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:
def attr_bitmap_expr(self, obj: str, cl: RInstance, index: int) -> str:
"""Return reference to the attribute definedness bitmap."""
cast = f"({cl.struct_name(self.names)} *)"
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 @@ -374,20 +380,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 @@ -407,26 +413,28 @@ 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:
if isinstance(rtype, RTuple):
return self.tuple_undefined_check_cond(
rtype, value, self.c_error_value, compare, check_exception=False
)
elif isinstance(rtype, RInstanceValue):
return f"{value}.vtable {compare} NULL"
else:
return f"{value} {compare} {self.c_error_value(rtype)}"

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

def rinstance_value_undefined_value(self, rinstance_value: RInstanceValue) -> str:
"""Undefined value for an unboxed instance."""
return f"(({rinstance_value.struct_name(self.names)}){self.c_initializer_undefined_value(rinstance_value)})"

def c_initializer_undefined_value(self, rtype: RType) -> str:
"""Undefined value represented in a form suitable for variable initialization."""
if isinstance(rtype, RTuple):
Expand All @@ -477,6 +489,8 @@ def c_initializer_undefined_value(self, rtype: RType) -> str:
return f"{{ {int_rprimitive.c_undefined} }}"
items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types])
return f"{{ {items} }}"
elif isinstance(rtype, RInstanceValue):
return "{0}"
else:
return self.c_undefined_value(rtype)

Expand All @@ -489,6 +503,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 @@ -987,7 +1003,17 @@ def emit_unbox(
self.emit_line("}")
if optional:
self.emit_line("}")
elif isinstance(typ, RInstanceValue):
if declare_dest:
self.emit_line(f"{self.ctype(typ)} {dest};")
if optional:
self.emit_line(f"if ({src} == NULL) {{")
self.emit_line(f"{dest} = {self.c_error_value(typ)};")
self.emit_line("} else {")

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

Expand Down Expand Up @@ -1041,6 +1067,31 @@ def emit_box(
inner_name = self.temp_name()
self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
elif isinstance(typ, RInstanceValue):
cl = typ.class_ir
generate_full = not cl.is_trait and not cl.builtin_base
assert generate_full, "Only full classes can be boxed" # only those have setup method
name_prefix = cl.name_prefix(self.names)
setup_name = f"{name_prefix}_setup"
py_type_struct = self.type_struct_name(cl)
temp_dest = self.temp_name()
self.emit_line(
f"{self.ctype_spaced(typ)}*{temp_dest} = ({self.ctype_spaced(typ)}*){setup_name}({py_type_struct});"
)
self.emit_line(f"if (unlikely({temp_dest} == NULL))")
self.emit_line(" CPyError_OutOfMemory();")
if cl.bitmap_attrs:
n_fields = (len(cl.bitmap_attrs) - 1) // BITMAP_BITS + 1
for i in range(n_fields):
attr_name = self.bitmap_field(i * BITMAP_BITS)
self.emit_line(f"{temp_dest}->{attr_name} = {src}.{attr_name};", ann="box")
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")
if attr_type.is_refcounted:
self.emit_inc_ref(temp_dest, attr_type)

self.emit_line(f"{declaration}{dest} = (PyObject *){temp_dest};")
else:
assert not typ.is_unboxed
# Type is boxed -- trivially just assign.
Expand All @@ -1054,6 +1105,8 @@ def emit_error_check(self, value: str, rtype: RType, failure: str) -> None:
else:
cond = self.tuple_undefined_check_cond(rtype, value, self.c_error_value, "==")
self.emit_line(f"if ({cond}) {{")
elif isinstance(rtype, RInstanceValue):
self.emit_line(f"if ({value}.vtable == NULL) {{")
elif rtype.error_overlap:
# The error value is also valid as a normal value, so we need to also check
# for a raised exception.
Expand Down
75 changes: 52 additions & 23 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
generate_len_wrapper,
generate_richcompare_wrapper,
generate_set_del_item_wrapper,
generate_str_wrapper,
)
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 RInstance, RInstanceValue, RTuple, RType, object_rprimitive
from mypyc.namegen import NameGenerator
from mypyc.sametype import is_same_type

Expand All @@ -44,8 +45,8 @@ def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
SLOT_DEFS: SlotTable = {
"__init__": ("tp_init", lambda c, t, e: generate_init_for_class(c, t, e)),
"__call__": ("tp_call", lambda c, t, e: generate_call_wrapper(c, t, e)),
"__str__": ("tp_str", native_slot),
"__repr__": ("tp_repr", native_slot),
"__str__": ("tp_str", generate_str_wrapper),
"__repr__": ("tp_repr", generate_str_wrapper),
"__next__": ("tp_iternext", native_slot),
"__iter__": ("tp_iter", native_slot),
"__hash__": ("tp_hash", generate_hash_wrapper),
Expand Down Expand Up @@ -293,7 +294,15 @@ 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.emit_line(f"static PyObject *{setup_name}(PyTypeObject *type);")
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 @@ -393,6 +402,7 @@ def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
if attr not in bitmap_attrs:
lines.append(f"{BITMAP_TYPE} {attr};")
bitmap_attrs.append(attr)

for attr, rtype in base.attributes.items():
if (attr, rtype) not in seen_attrs:
lines.append(f"{emitter.ctype_spaced(rtype)}{emitter.attr(attr)};")
Expand Down Expand Up @@ -554,7 +564,7 @@ def generate_setup_for_class(
emitter: Emitter,
) -> None:
"""Generate a native function that allocates an instance of a class."""
emitter.emit_line("static PyObject *")
emitter.emit_line("PyObject *")
emitter.emit_line(f"{func_name}(PyTypeObject *type)")
emitter.emit_line("{")
emitter.emit_line(f"{cl.struct_name(emitter.names)} *self;")
Expand Down Expand Up @@ -585,7 +595,7 @@ def generate_setup_for_class(

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

# Initialize attributes to default values, if necessary
Expand Down Expand Up @@ -614,10 +624,15 @@ def generate_constructor_for_class(
"""Generate a native function that allocates and initializes an instance of a class."""
emitter.emit_line(f"{native_function_header(fn, emitter)}")
emitter.emit_line("{")
emitter.emit_line(f"PyObject *self = {setup_name}({emitter.type_struct_name(cl)});")
emitter.emit_line("if (self == NULL)")
emitter.emit_line(" return NULL;")
args = ", ".join(["self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
if cl.is_value_type:
emitter.emit_line(f"{cl.struct_name(emitter.names)} self = {{0}};")
emitter.emit_line(f"self.vtable = {vtable_name};")
args = ", ".join(["(PyObject*)&self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
else:
emitter.emit_line(f"PyObject *self = {setup_name}({emitter.type_struct_name(cl)});")
emitter.emit_line("if (self == NULL)")
emitter.emit_line(" return NULL;")
args = ", ".join(["self"] + [REG_PREFIX + arg.name for arg in fn.sig.args])
if init_fn is not None:
emitter.emit_line(
"char res = {}{}{}({});".format(
Expand All @@ -628,17 +643,25 @@ def generate_constructor_for_class(
)
)
emitter.emit_line("if (res == 2) {")
emitter.emit_line("Py_DECREF(self);")
emitter.emit_line("return NULL;")
if cl.is_value_type:
emitter.emit_line("self.vtable = NULL;")
emitter.emit_line("return self;")
else:
emitter.emit_line("Py_DECREF(self);")
emitter.emit_line("return NULL;")
emitter.emit_line("}")

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

emitter.emit_line("if (res < 0) {")
emitter.emit_line("Py_DECREF(self);")
emitter.emit_line("return NULL;")
if cl.is_value_type:
emitter.emit_line("self.vtable = NULL;")
emitter.emit_line("return self;")
else:
emitter.emit_line("Py_DECREF(self);")
emitter.emit_line("return NULL;")
emitter.emit_line("}")

emitter.emit_line("return self;")
Expand Down Expand Up @@ -931,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 @@ -970,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 @@ -988,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 @@ -1009,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
Loading
Loading