Skip to content

[mypyc] Fix __dict__ in CPython 3.11 #13206

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

Closed
wants to merge 20 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 86 additions & 30 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,21 +257,40 @@ def emit_line() -> None:

# XXX: there is no reason for the __weakref__ stuff to be mixed up with __dict__
if cl.has_dict:
# __dict__ lives right after the struct and __weakref__ lives right after that
# TODO: They should get members in the struct instead of doing this nonsense.
weak_offset = f"{base_size} + sizeof(PyObject *)"
emitter.emit_lines(
f"PyMemberDef {members_name}[] = {{",
f'{{"__dict__", T_OBJECT_EX, {base_size}, 0, NULL}},',
f'{{"__weakref__", T_OBJECT_EX, {weak_offset}, 0, NULL}},',
"{0}",
"};",
)
# TODO: Use PY_VERSION_HEX and include patchlevel.h
if emitter.capi_version < (3, 11):
# __dict__ lives right after the struct and __weakref__ lives right after that
# TODO: They should get members in the struct instead of doing this nonsense.
weak_offset = f"{base_size} + sizeof(PyObject *)"
emitter.emit_lines(
f"PyMemberDef {members_name}[] = {{",
f'{{"__dict__", T_OBJECT_EX, {base_size}, 0, NULL}},',
f'{{"__weakref__", T_OBJECT_EX, {weak_offset}, 0, NULL}},',
"{0}",
"};",
)

fields["tp_members"] = members_name
fields["tp_basicsize"] = f"{base_size} + 2*sizeof(PyObject *)"
fields["tp_dictoffset"] = base_size
fields["tp_weaklistoffset"] = weak_offset

else:
# In CPython >=3.11, __dict__ slot is automatically set before the type. So
# we need to let CPython manage it.
# __weakref__ lives right after the struct
# TODO: __weakref__ should get members in the struct instead of doing this nonsense.
weak_offset = f"{base_size}"
emitter.emit_lines(
f"PyMemberDef {members_name}[] = {{",
f'{{"__weakref__", T_OBJECT_EX, {weak_offset}, 0, NULL}},',
"{0}",
"};",
)

fields["tp_members"] = members_name
fields["tp_basicsize"] = f"{base_size} + 2*sizeof(PyObject *)"
fields["tp_dictoffset"] = base_size
fields["tp_weaklistoffset"] = weak_offset
fields["tp_members"] = members_name
fields["tp_basicsize"] = f"{base_size} + sizeof(PyObject *)"
fields["tp_weaklistoffset"] = weak_offset
else:
fields["tp_basicsize"] = base_size

Expand Down Expand Up @@ -327,6 +346,9 @@ def emit_line() -> None:
# This is just a placeholder to please CPython. It will be
# overriden during setup.
fields["tp_call"] = "PyVectorcall_Call"
# TODO: Use PY_VERSION_HEX and include patchlevel.h
if cl.has_dict and emitter.capi_version >= (3, 11):
flags.append("Py_TPFLAGS_MANAGED_DICT")
fields["tp_flags"] = " | ".join(flags)

emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")
Expand Down Expand Up @@ -707,14 +729,35 @@ def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -
emitter.emit_gc_visit(f"self->{emitter.attr(attr)}", rtype)
if cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_visit(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name), object_rprimitive
)
emitter.emit_gc_visit(
"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))".format(struct_name),
object_rprimitive,
)
# TODO: Use PY_VERSION_HEX and include patchlevel.h
if emitter.capi_version < (3, 11):
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_visit(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name),
object_rprimitive,
)
emitter.emit_gc_visit(
"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))".format(
struct_name
),
object_rprimitive,
)
else:
# In >=3.11, __dict__ is managed by CPython and __weakref__ lives right after
# the struct
emitter.emit_line("{")
emitter.emit_line(
"int vret = _PyObject_VisitManagedDict((PyObject *)self, visit, arg);"
)
emitter.emit_line("if (vret)")
emitter.emit_line("{")
emitter.emit_line("return vret;")
emitter.emit_line("}")
emitter.emit_line("}")
emitter.emit_gc_visit(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name),
object_rprimitive,
)
emitter.emit_line("return 0;")
emitter.emit_line("}")

Expand All @@ -728,14 +771,27 @@ def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> N
emitter.emit_gc_clear(f"self->{emitter.attr(attr)}", rtype)
if cl.has_dict:
struct_name = cl.struct_name(emitter.names)
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_clear(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name), object_rprimitive
)
emitter.emit_gc_clear(
"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))".format(struct_name),
object_rprimitive,
)
# TODO: Use PY_VERSION_HEX and include patchlevel.h
if emitter.capi_version < (3, 11):
# __dict__ lives right after the struct and __weakref__ lives right after that
emitter.emit_gc_clear(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name),
object_rprimitive,
)
emitter.emit_gc_clear(
"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({})))".format(
struct_name
),
object_rprimitive,
)
else:
# In >=3.11, __dict__ is managed by CPython and __weakref__ lives right after
# the struct
emitter.emit_line("_PyObject_ClearManagedDict((PyObject *)self);")
emitter.emit_gc_clear(
"*((PyObject **)((char *)self + sizeof({})))".format(struct_name),
object_rprimitive,
)
emitter.emit_line("return 0;")
emitter.emit_line("}")

Expand Down