| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061 |
- """Code generation for native classes and related wrappers."""
- from __future__ import annotations
- from typing import Callable, Mapping, Tuple
- from mypyc.codegen.emit import Emitter, HeaderDeclaration, ReturnHandler
- from mypyc.codegen.emitfunc import native_function_header
- from mypyc.codegen.emitwrapper import (
- generate_bin_op_wrapper,
- generate_bool_wrapper,
- generate_contains_wrapper,
- generate_dunder_wrapper,
- generate_get_wrapper,
- generate_hash_wrapper,
- generate_ipow_wrapper,
- generate_len_wrapper,
- generate_richcompare_wrapper,
- generate_set_del_item_wrapper,
- )
- from mypyc.common import BITMAP_BITS, BITMAP_TYPE, NATIVE_PREFIX, PREFIX, REG_PREFIX, use_fastcall
- 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.namegen import NameGenerator
- from mypyc.sametype import is_same_type
- def native_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
- return f"{NATIVE_PREFIX}{fn.cname(emitter.names)}"
- def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
- return f"{PREFIX}{fn.cname(emitter.names)}"
- # We maintain a table from dunder function names to struct slots they
- # correspond to and functions that generate a wrapper (if necessary)
- # and return the function name to stick in the slot.
- # TODO: Add remaining dunder methods
- SlotGenerator = Callable[[ClassIR, FuncIR, Emitter], str]
- SlotTable = Mapping[str, Tuple[str, SlotGenerator]]
- 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),
- "__next__": ("tp_iternext", native_slot),
- "__iter__": ("tp_iter", native_slot),
- "__hash__": ("tp_hash", generate_hash_wrapper),
- "__get__": ("tp_descr_get", generate_get_wrapper),
- }
- AS_MAPPING_SLOT_DEFS: SlotTable = {
- "__getitem__": ("mp_subscript", generate_dunder_wrapper),
- "__setitem__": ("mp_ass_subscript", generate_set_del_item_wrapper),
- "__delitem__": ("mp_ass_subscript", generate_set_del_item_wrapper),
- "__len__": ("mp_length", generate_len_wrapper),
- }
- AS_SEQUENCE_SLOT_DEFS: SlotTable = {"__contains__": ("sq_contains", generate_contains_wrapper)}
- AS_NUMBER_SLOT_DEFS: SlotTable = {
- # Unary operations.
- "__bool__": ("nb_bool", generate_bool_wrapper),
- "__int__": ("nb_int", generate_dunder_wrapper),
- "__float__": ("nb_float", generate_dunder_wrapper),
- "__neg__": ("nb_negative", generate_dunder_wrapper),
- "__pos__": ("nb_positive", generate_dunder_wrapper),
- "__abs__": ("nb_absolute", generate_dunder_wrapper),
- "__invert__": ("nb_invert", generate_dunder_wrapper),
- # Binary operations.
- "__add__": ("nb_add", generate_bin_op_wrapper),
- "__radd__": ("nb_add", generate_bin_op_wrapper),
- "__sub__": ("nb_subtract", generate_bin_op_wrapper),
- "__rsub__": ("nb_subtract", generate_bin_op_wrapper),
- "__mul__": ("nb_multiply", generate_bin_op_wrapper),
- "__rmul__": ("nb_multiply", generate_bin_op_wrapper),
- "__mod__": ("nb_remainder", generate_bin_op_wrapper),
- "__rmod__": ("nb_remainder", generate_bin_op_wrapper),
- "__truediv__": ("nb_true_divide", generate_bin_op_wrapper),
- "__rtruediv__": ("nb_true_divide", generate_bin_op_wrapper),
- "__floordiv__": ("nb_floor_divide", generate_bin_op_wrapper),
- "__rfloordiv__": ("nb_floor_divide", generate_bin_op_wrapper),
- "__divmod__": ("nb_divmod", generate_bin_op_wrapper),
- "__rdivmod__": ("nb_divmod", generate_bin_op_wrapper),
- "__lshift__": ("nb_lshift", generate_bin_op_wrapper),
- "__rlshift__": ("nb_lshift", generate_bin_op_wrapper),
- "__rshift__": ("nb_rshift", generate_bin_op_wrapper),
- "__rrshift__": ("nb_rshift", generate_bin_op_wrapper),
- "__and__": ("nb_and", generate_bin_op_wrapper),
- "__rand__": ("nb_and", generate_bin_op_wrapper),
- "__or__": ("nb_or", generate_bin_op_wrapper),
- "__ror__": ("nb_or", generate_bin_op_wrapper),
- "__xor__": ("nb_xor", generate_bin_op_wrapper),
- "__rxor__": ("nb_xor", generate_bin_op_wrapper),
- "__matmul__": ("nb_matrix_multiply", generate_bin_op_wrapper),
- "__rmatmul__": ("nb_matrix_multiply", generate_bin_op_wrapper),
- # In-place binary operations.
- "__iadd__": ("nb_inplace_add", generate_dunder_wrapper),
- "__isub__": ("nb_inplace_subtract", generate_dunder_wrapper),
- "__imul__": ("nb_inplace_multiply", generate_dunder_wrapper),
- "__imod__": ("nb_inplace_remainder", generate_dunder_wrapper),
- "__itruediv__": ("nb_inplace_true_divide", generate_dunder_wrapper),
- "__ifloordiv__": ("nb_inplace_floor_divide", generate_dunder_wrapper),
- "__ilshift__": ("nb_inplace_lshift", generate_dunder_wrapper),
- "__irshift__": ("nb_inplace_rshift", generate_dunder_wrapper),
- "__iand__": ("nb_inplace_and", generate_dunder_wrapper),
- "__ior__": ("nb_inplace_or", generate_dunder_wrapper),
- "__ixor__": ("nb_inplace_xor", generate_dunder_wrapper),
- "__imatmul__": ("nb_inplace_matrix_multiply", generate_dunder_wrapper),
- # Ternary operations. (yes, really)
- # These are special cased in generate_bin_op_wrapper().
- "__pow__": ("nb_power", generate_bin_op_wrapper),
- "__rpow__": ("nb_power", generate_bin_op_wrapper),
- "__ipow__": ("nb_inplace_power", generate_ipow_wrapper),
- }
- AS_ASYNC_SLOT_DEFS: SlotTable = {
- "__await__": ("am_await", native_slot),
- "__aiter__": ("am_aiter", native_slot),
- "__anext__": ("am_anext", native_slot),
- }
- SIDE_TABLES = [
- ("as_mapping", "PyMappingMethods", AS_MAPPING_SLOT_DEFS),
- ("as_sequence", "PySequenceMethods", AS_SEQUENCE_SLOT_DEFS),
- ("as_number", "PyNumberMethods", AS_NUMBER_SLOT_DEFS),
- ("as_async", "PyAsyncMethods", AS_ASYNC_SLOT_DEFS),
- ]
- # Slots that need to always be filled in because they don't get
- # inherited right.
- ALWAYS_FILL = {"__hash__"}
- def generate_call_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
- if emitter.use_vectorcall():
- # Use vectorcall wrapper if supported (PEP 590).
- return "PyVectorcall_Call"
- else:
- # On older Pythons use the legacy wrapper.
- return wrapper_slot(cl, fn, emitter)
- def slot_key(attr: str) -> str:
- """Map dunder method name to sort key.
- Sort reverse operator methods and __delitem__ after others ('x' > '_').
- """
- if (attr.startswith("__r") and attr != "__rshift__") or attr == "__delitem__":
- return "x" + attr
- return attr
- def generate_slots(cl: ClassIR, table: SlotTable, emitter: Emitter) -> dict[str, str]:
- fields: dict[str, str] = {}
- generated: dict[str, str] = {}
- # Sort for determinism on Python 3.5
- for name, (slot, generator) in sorted(table.items(), key=lambda x: slot_key(x[0])):
- method_cls = cl.get_method_and_class(name)
- if method_cls and (method_cls[1] == cl or name in ALWAYS_FILL):
- if slot in generated:
- # Reuse previously generated wrapper.
- fields[slot] = generated[slot]
- else:
- # Generate new wrapper.
- name = generator(cl, method_cls[0], emitter)
- fields[slot] = name
- generated[slot] = name
- return fields
- def generate_class_type_decl(
- cl: ClassIR, c_emitter: Emitter, external_emitter: Emitter, emitter: Emitter
- ) -> None:
- context = c_emitter.context
- name = emitter.type_struct_name(cl)
- context.declarations[name] = HeaderDeclaration(
- f"PyTypeObject *{emitter.type_struct_name(cl)};", needs_export=True
- )
- # If this is a non-extension class, all we want is the type object decl.
- if not cl.is_ext_class:
- return
- generate_object_struct(cl, external_emitter)
- generate_full = not cl.is_trait and not cl.builtin_base
- if generate_full:
- context.declarations[emitter.native_function_name(cl.ctor)] = HeaderDeclaration(
- f"{native_function_header(cl.ctor, emitter)};", needs_export=True
- )
- def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
- """Generate C code for a class.
- This is the main entry point to the module.
- """
- name = cl.name
- name_prefix = cl.name_prefix(emitter.names)
- setup_name = f"{name_prefix}_setup"
- new_name = f"{name_prefix}_new"
- members_name = f"{name_prefix}_members"
- getseters_name = f"{name_prefix}_getseters"
- vtable_name = f"{name_prefix}_vtable"
- traverse_name = f"{name_prefix}_traverse"
- clear_name = f"{name_prefix}_clear"
- dealloc_name = f"{name_prefix}_dealloc"
- methods_name = f"{name_prefix}_methods"
- vtable_setup_name = f"{name_prefix}_trait_vtable_setup"
- fields: dict[str, str] = {}
- fields["tp_name"] = f'"{name}"'
- generate_full = not cl.is_trait and not cl.builtin_base
- needs_getseters = cl.needs_getseters or not cl.is_generated
- if not cl.builtin_base:
- fields["tp_new"] = new_name
- if generate_full:
- fields["tp_dealloc"] = f"(destructor){name_prefix}_dealloc"
- fields["tp_traverse"] = f"(traverseproc){name_prefix}_traverse"
- fields["tp_clear"] = f"(inquiry){name_prefix}_clear"
- if needs_getseters:
- fields["tp_getset"] = getseters_name
- fields["tp_methods"] = methods_name
- def emit_line() -> None:
- emitter.emit_line()
- emit_line()
- # If the class has a method to initialize default attribute
- # values, we need to call it during initialization.
- defaults_fn = cl.get_method("__mypyc_defaults_setup")
- # If there is a __init__ method, we'll use it in the native constructor.
- init_fn = cl.get_method("__init__")
- # Fill out slots in the type object from dunder methods.
- fields.update(generate_slots(cl, SLOT_DEFS, emitter))
- # Fill out dunder methods that live in tables hanging off the side.
- for table_name, type, slot_defs in SIDE_TABLES:
- slots = generate_slots(cl, slot_defs, emitter)
- if slots:
- table_struct_name = generate_side_table_for_class(cl, table_name, type, slots, emitter)
- fields[f"tp_{table_name}"] = f"&{table_struct_name}"
- richcompare_name = generate_richcompare_wrapper(cl, emitter)
- if richcompare_name:
- fields["tp_richcompare"] = richcompare_name
- # If the class inherits from python, make space for a __dict__
- struct_name = cl.struct_name(emitter.names)
- if cl.builtin_base:
- base_size = f"sizeof({cl.builtin_base})"
- elif cl.is_trait:
- base_size = "sizeof(PyObject)"
- else:
- base_size = f"sizeof({struct_name})"
- # Since our types aren't allocated using type() we need to
- # populate these fields ourselves if we want them to have correct
- # values. PyType_Ready will inherit the offsets from tp_base but
- # that isn't what we want.
- # XXX: there is no reason for the __weakref__ stuff to be mixed up with __dict__
- if cl.has_dict and not has_managed_dict(cl, emitter):
- # __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 *)"
- if emitter.capi_version < (3, 12):
- fields["tp_dictoffset"] = base_size
- fields["tp_weaklistoffset"] = weak_offset
- else:
- fields["tp_basicsize"] = base_size
- if generate_full:
- # 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);")
- assert cl.ctor is not None
- emitter.emit_line(native_function_header(cl.ctor, emitter) + ";")
- emit_line()
- init_fn = cl.get_method("__init__")
- generate_new_for_class(cl, new_name, vtable_name, setup_name, init_fn, emitter)
- emit_line()
- generate_traverse_for_class(cl, traverse_name, emitter)
- emit_line()
- generate_clear_for_class(cl, clear_name, emitter)
- emit_line()
- generate_dealloc_for_class(cl, dealloc_name, clear_name, emitter)
- emit_line()
- if cl.allow_interpreted_subclasses:
- shadow_vtable_name: str | None = generate_vtables(
- cl, vtable_setup_name + "_shadow", vtable_name + "_shadow", emitter, shadow=True
- )
- emit_line()
- else:
- shadow_vtable_name = None
- vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False)
- emit_line()
- if needs_getseters:
- generate_getseter_declarations(cl, emitter)
- emit_line()
- generate_getseters_table(cl, getseters_name, emitter)
- emit_line()
- if cl.is_trait:
- generate_new_for_trait(cl, new_name, emitter)
- generate_methods_table(cl, methods_name, emitter)
- emit_line()
- flags = ["Py_TPFLAGS_DEFAULT", "Py_TPFLAGS_HEAPTYPE", "Py_TPFLAGS_BASETYPE"]
- if generate_full:
- flags.append("Py_TPFLAGS_HAVE_GC")
- if cl.has_method("__call__") and emitter.use_vectorcall():
- fields["tp_vectorcall_offset"] = "offsetof({}, vectorcall)".format(
- cl.struct_name(emitter.names)
- )
- flags.append("_Py_TPFLAGS_HAVE_VECTORCALL")
- if not fields.get("tp_vectorcall"):
- # This is just a placeholder to please CPython. It will be
- # overridden during setup.
- fields["tp_call"] = "PyVectorcall_Call"
- if has_managed_dict(cl, emitter):
- flags.append("Py_TPFLAGS_MANAGED_DICT")
- fields["tp_flags"] = " | ".join(flags)
- emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")
- emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)")
- for field, value in fields.items():
- emitter.emit_line(f".{field} = {value},")
- emitter.emit_line("};")
- emitter.emit_line(
- "static PyTypeObject *{t}_template = &{t}_template_;".format(
- t=emitter.type_struct_name(cl)
- )
- )
- emitter.emit_line()
- if generate_full:
- generate_setup_for_class(
- cl, setup_name, defaults_fn, vtable_name, shadow_vtable_name, emitter
- )
- emitter.emit_line()
- generate_constructor_for_class(cl, cl.ctor, init_fn, setup_name, vtable_name, emitter)
- emitter.emit_line()
- if needs_getseters:
- generate_getseters(cl, emitter)
- def getter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
- return names.private_name(cl.module_name, f"{cl.name}_get_{attribute}")
- def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
- return names.private_name(cl.module_name, f"{cl.name}_set_{attribute}")
- def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
- seen_attrs: set[tuple[str, RType]] = set()
- lines: list[str] = []
- lines += ["typedef struct {", "PyObject_HEAD", "CPyVTableItem *vtable;"]
- if cl.has_method("__call__") and emitter.use_vectorcall():
- lines.append("vectorcallfunc vectorcall;")
- bitmap_attrs = []
- for base in reversed(cl.base_mro):
- if not base.is_trait:
- if base.bitmap_attrs:
- # Do we need another attribute bitmap field?
- if emitter.bitmap_field(len(base.bitmap_attrs) - 1) not in bitmap_attrs:
- for i in range(0, len(base.bitmap_attrs), BITMAP_BITS):
- attr = emitter.bitmap_field(i)
- 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)};")
- seen_attrs.add((attr, rtype))
- if isinstance(rtype, RTuple):
- emitter.declare_tuple_struct(rtype)
- lines.append(f"}} {cl.struct_name(emitter.names)};")
- lines.append("")
- emitter.context.declarations[cl.struct_name(emitter.names)] = HeaderDeclaration(
- lines, is_type=True
- )
- def generate_vtables(
- base: ClassIR, vtable_setup_name: str, vtable_name: str, emitter: Emitter, shadow: bool
- ) -> str:
- """Emit the vtables and vtable setup functions for a class.
- This includes both the primary vtable and any trait implementation vtables.
- The trait vtables go before the main vtable, and have the following layout:
- {
- CPyType_T1, // pointer to type object
- C_T1_trait_vtable, // pointer to array of method pointers
- C_T1_offset_table, // pointer to array of attribute offsets
- CPyType_T2,
- C_T2_trait_vtable,
- C_T2_offset_table,
- ...
- }
- The method implementations are calculated at the end of IR pass, attribute
- offsets are {offsetof(native__C, _x1), offsetof(native__C, _y1), ...}.
- To account for both dynamic loading and dynamic class creation,
- vtables are populated dynamically at class creation time, so we
- emit empty array definitions to store the vtables and a function to
- populate them.
- If shadow is True, generate "shadow vtables" that point to the
- shadow glue methods (which should dispatch via the Python C-API).
- Returns the expression to use to refer to the vtable, which might be
- different than the name, if there are trait vtables.
- """
- def trait_vtable_name(trait: ClassIR) -> str:
- return "{}_{}_trait_vtable{}".format(
- base.name_prefix(emitter.names),
- trait.name_prefix(emitter.names),
- "_shadow" if shadow else "",
- )
- def trait_offset_table_name(trait: ClassIR) -> str:
- return "{}_{}_offset_table".format(
- base.name_prefix(emitter.names), trait.name_prefix(emitter.names)
- )
- # Emit array definitions with enough space for all the entries
- emitter.emit_line(
- "static CPyVTableItem {}[{}];".format(
- vtable_name, max(1, len(base.vtable_entries) + 3 * len(base.trait_vtables))
- )
- )
- for trait, vtable in base.trait_vtables.items():
- # Trait methods entry (vtable index -> method implementation).
- emitter.emit_line(
- f"static CPyVTableItem {trait_vtable_name(trait)}[{max(1, len(vtable))}];"
- )
- # Trait attributes entry (attribute number in trait -> offset in actual struct).
- emitter.emit_line(
- "static size_t {}[{}];".format(
- trait_offset_table_name(trait), max(1, len(trait.attributes))
- )
- )
- # Emit vtable setup function
- emitter.emit_line("static bool")
- emitter.emit_line(f"{NATIVE_PREFIX}{vtable_setup_name}(void)")
- emitter.emit_line("{")
- if base.allow_interpreted_subclasses and not shadow:
- emitter.emit_line(f"{NATIVE_PREFIX}{vtable_setup_name}_shadow();")
- subtables = []
- for trait, vtable in base.trait_vtables.items():
- name = trait_vtable_name(trait)
- offset_name = trait_offset_table_name(trait)
- generate_vtable(vtable, name, emitter, [], shadow)
- generate_offset_table(offset_name, emitter, trait, base)
- subtables.append((trait, name, offset_name))
- generate_vtable(base.vtable_entries, vtable_name, emitter, subtables, shadow)
- emitter.emit_line("return 1;")
- emitter.emit_line("}")
- return vtable_name if not subtables else f"{vtable_name} + {len(subtables) * 3}"
- def generate_offset_table(
- trait_offset_table_name: str, emitter: Emitter, trait: ClassIR, cl: ClassIR
- ) -> None:
- """Generate attribute offset row of a trait vtable."""
- emitter.emit_line(f"size_t {trait_offset_table_name}_scratch[] = {{")
- for attr in trait.attributes:
- emitter.emit_line(f"offsetof({cl.struct_name(emitter.names)}, {emitter.attr(attr)}),")
- if not trait.attributes:
- # This is for msvc.
- emitter.emit_line("0")
- emitter.emit_line("};")
- emitter.emit_line(
- "memcpy({name}, {name}_scratch, sizeof({name}));".format(name=trait_offset_table_name)
- )
- def generate_vtable(
- entries: VTableEntries,
- vtable_name: str,
- emitter: Emitter,
- subtables: list[tuple[ClassIR, str, str]],
- shadow: bool,
- ) -> None:
- emitter.emit_line(f"CPyVTableItem {vtable_name}_scratch[] = {{")
- if subtables:
- emitter.emit_line("/* Array of trait vtables */")
- for trait, table, offset_table in subtables:
- emitter.emit_line(
- "(CPyVTableItem){}, (CPyVTableItem){}, (CPyVTableItem){},".format(
- emitter.type_struct_name(trait), table, offset_table
- )
- )
- emitter.emit_line("/* Start of real vtable */")
- for entry in entries:
- method = entry.shadow_method if shadow and entry.shadow_method else entry.method
- emitter.emit_line(
- "(CPyVTableItem){}{}{},".format(
- emitter.get_group_prefix(entry.method.decl),
- NATIVE_PREFIX,
- method.cname(emitter.names),
- )
- )
- # msvc doesn't allow empty arrays; maybe allowing them at all is an extension?
- if not entries:
- emitter.emit_line("NULL")
- emitter.emit_line("};")
- emitter.emit_line("memcpy({name}, {name}_scratch, sizeof({name}));".format(name=vtable_name))
- def generate_setup_for_class(
- cl: ClassIR,
- func_name: str,
- defaults_fn: FuncIR | None,
- vtable_name: str,
- shadow_vtable_name: str | None,
- emitter: Emitter,
- ) -> None:
- """Generate a native function that allocates an instance of a class."""
- emitter.emit_line("static PyObject *")
- emitter.emit_line(f"{func_name}(PyTypeObject *type)")
- emitter.emit_line("{")
- emitter.emit_line(f"{cl.struct_name(emitter.names)} *self;")
- emitter.emit_line(f"self = ({cl.struct_name(emitter.names)} *)type->tp_alloc(type, 0);")
- emitter.emit_line("if (self == NULL)")
- emitter.emit_line(" return NULL;")
- if shadow_vtable_name:
- emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
- emitter.emit_line(f"self->vtable = {shadow_vtable_name};")
- emitter.emit_line("} else {")
- emitter.emit_line(f"self->vtable = {vtable_name};")
- emitter.emit_line("}")
- else:
- emitter.emit_line(f"self->vtable = {vtable_name};")
- for i in range(0, len(cl.bitmap_attrs), BITMAP_BITS):
- field = emitter.bitmap_field(i)
- emitter.emit_line(f"self->{field} = 0;")
- if cl.has_method("__call__") and emitter.use_vectorcall():
- name = cl.method_decl("__call__").cname(emitter.names)
- emitter.emit_line(f"self->vectorcall = {PREFIX}{name};")
- for base in reversed(cl.base_mro):
- for attr, rtype in base.attributes.items():
- value = emitter.c_undefined_value(rtype)
- # We don't need to set this field to NULL since tp_alloc() already
- # zero-initializes `self`.
- if value != "NULL":
- emitter.emit_line(rf"self->{emitter.attr(attr)} = {value};")
- # Initialize attributes to default values, if necessary
- if defaults_fn is not None:
- emitter.emit_lines(
- "if ({}{}((PyObject *)self) == 0) {{".format(
- NATIVE_PREFIX, defaults_fn.cname(emitter.names)
- ),
- "Py_DECREF(self);",
- "return NULL;",
- "}",
- )
- emitter.emit_line("return (PyObject *)self;")
- emitter.emit_line("}")
- def generate_constructor_for_class(
- cl: ClassIR,
- fn: FuncDecl,
- init_fn: FuncIR | None,
- setup_name: str,
- vtable_name: str,
- emitter: Emitter,
- ) -> None:
- """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 init_fn is not None:
- emitter.emit_line(
- "char res = {}{}{}({});".format(
- emitter.get_group_prefix(init_fn.decl),
- NATIVE_PREFIX,
- init_fn.cname(emitter.names),
- args,
- )
- )
- emitter.emit_line("if (res == 2) {")
- 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;")
- emitter.emit_line("}")
- emitter.emit_line("return self;")
- emitter.emit_line("}")
- def generate_init_for_class(cl: ClassIR, init_fn: FuncIR, emitter: Emitter) -> str:
- """Generate an init function suitable for use as tp_init.
- tp_init needs to be a function that returns an int, and our
- __init__ methods return a PyObject. Translate NULL to -1,
- everything else to 0.
- """
- func_name = f"{cl.name_prefix(emitter.names)}_init"
- emitter.emit_line("static int")
- emitter.emit_line(f"{func_name}(PyObject *self, PyObject *args, PyObject *kwds)")
- emitter.emit_line("{")
- if cl.allow_interpreted_subclasses or cl.builtin_base:
- emitter.emit_line(
- "return {}{}(self, args, kwds) != NULL ? 0 : -1;".format(
- PREFIX, init_fn.cname(emitter.names)
- )
- )
- else:
- emitter.emit_line("return 0;")
- emitter.emit_line("}")
- return func_name
- def generate_new_for_class(
- cl: ClassIR,
- func_name: str,
- vtable_name: str,
- setup_name: str,
- init_fn: FuncIR | None,
- emitter: Emitter,
- ) -> None:
- emitter.emit_line("static PyObject *")
- emitter.emit_line(f"{func_name}(PyTypeObject *type, PyObject *args, PyObject *kwds)")
- emitter.emit_line("{")
- # TODO: Check and unbox arguments
- if not cl.allow_interpreted_subclasses:
- emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
- emitter.emit_line(
- 'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");'
- )
- emitter.emit_line("return NULL;")
- emitter.emit_line("}")
- if not init_fn or cl.allow_interpreted_subclasses or cl.builtin_base or cl.is_serializable():
- # Match Python semantics -- __new__ doesn't call __init__.
- emitter.emit_line(f"return {setup_name}(type);")
- else:
- # __new__ of a native class implicitly calls __init__ so that we
- # can enforce that instances are always properly initialized. This
- # is needed to support always defined attributes.
- emitter.emit_line(f"PyObject *self = {setup_name}(type);")
- emitter.emit_lines("if (self == NULL)", " return NULL;")
- emitter.emit_line(
- f"PyObject *ret = {PREFIX}{init_fn.cname(emitter.names)}(self, args, kwds);"
- )
- emitter.emit_lines("if (ret == NULL)", " return NULL;")
- emitter.emit_line("return self;")
- emitter.emit_line("}")
- def generate_new_for_trait(cl: ClassIR, func_name: str, emitter: Emitter) -> None:
- emitter.emit_line("static PyObject *")
- emitter.emit_line(f"{func_name}(PyTypeObject *type, PyObject *args, PyObject *kwds)")
- emitter.emit_line("{")
- emitter.emit_line(f"if (type != {emitter.type_struct_name(cl)}) {{")
- emitter.emit_line(
- "PyErr_SetString(PyExc_TypeError, "
- '"interpreted classes cannot inherit from compiled traits");'
- )
- emitter.emit_line("} else {")
- emitter.emit_line('PyErr_SetString(PyExc_TypeError, "traits may not be directly created");')
- emitter.emit_line("}")
- emitter.emit_line("return NULL;")
- emitter.emit_line("}")
- def generate_traverse_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> None:
- """Emit function that performs cycle GC traversal of an instance."""
- emitter.emit_line("static int")
- emitter.emit_line(
- f"{func_name}({cl.struct_name(emitter.names)} *self, visitproc visit, void *arg)"
- )
- emitter.emit_line("{")
- for base in reversed(cl.base_mro):
- for attr, rtype in base.attributes.items():
- emitter.emit_gc_visit(f"self->{emitter.attr(attr)}", rtype)
- if has_managed_dict(cl, emitter):
- emitter.emit_line("_PyObject_VisitManagedDict((PyObject *)self, visit, arg);")
- elif 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(
- f"*((PyObject **)((char *)self + sizeof({struct_name})))", object_rprimitive
- )
- emitter.emit_gc_visit(
- f"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({struct_name})))",
- object_rprimitive,
- )
- emitter.emit_line("return 0;")
- emitter.emit_line("}")
- def generate_clear_for_class(cl: ClassIR, func_name: str, emitter: Emitter) -> None:
- emitter.emit_line("static int")
- emitter.emit_line(f"{func_name}({cl.struct_name(emitter.names)} *self)")
- emitter.emit_line("{")
- for base in reversed(cl.base_mro):
- for attr, rtype in base.attributes.items():
- emitter.emit_gc_clear(f"self->{emitter.attr(attr)}", rtype)
- if has_managed_dict(cl, emitter):
- emitter.emit_line("_PyObject_ClearManagedDict((PyObject *)self);")
- elif 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(
- f"*((PyObject **)((char *)self + sizeof({struct_name})))", object_rprimitive
- )
- emitter.emit_gc_clear(
- f"*((PyObject **)((char *)self + sizeof(PyObject *) + sizeof({struct_name})))",
- object_rprimitive,
- )
- emitter.emit_line("return 0;")
- emitter.emit_line("}")
- def generate_dealloc_for_class(
- cl: ClassIR, dealloc_func_name: str, clear_func_name: str, emitter: Emitter
- ) -> None:
- emitter.emit_line("static void")
- emitter.emit_line(f"{dealloc_func_name}({cl.struct_name(emitter.names)} *self)")
- emitter.emit_line("{")
- emitter.emit_line("PyObject_GC_UnTrack(self);")
- # The trashcan is needed to handle deep recursive deallocations
- emitter.emit_line(f"CPy_TRASHCAN_BEGIN(self, {dealloc_func_name})")
- emitter.emit_line(f"{clear_func_name}(self);")
- emitter.emit_line("Py_TYPE(self)->tp_free((PyObject *)self);")
- emitter.emit_line("CPy_TRASHCAN_END(self)")
- emitter.emit_line("}")
- def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None:
- emitter.emit_line(f"static PyMethodDef {name}[] = {{")
- for fn in cl.methods.values():
- if fn.decl.is_prop_setter or fn.decl.is_prop_getter:
- continue
- emitter.emit_line(f'{{"{fn.name}",')
- emitter.emit_line(f" (PyCFunction){PREFIX}{fn.cname(emitter.names)},")
- if use_fastcall(emitter.capi_version):
- flags = ["METH_FASTCALL"]
- else:
- flags = ["METH_VARARGS"]
- flags.append("METH_KEYWORDS")
- if fn.decl.kind == FUNC_STATICMETHOD:
- flags.append("METH_STATIC")
- elif fn.decl.kind == FUNC_CLASSMETHOD:
- flags.append("METH_CLASS")
- emitter.emit_line(" {}, NULL}},".format(" | ".join(flags)))
- # Provide a default __getstate__ and __setstate__
- if not cl.has_method("__setstate__") and not cl.has_method("__getstate__"):
- emitter.emit_lines(
- '{"__setstate__", (PyCFunction)CPyPickle_SetState, METH_O, NULL},',
- '{"__getstate__", (PyCFunction)CPyPickle_GetState, METH_NOARGS, NULL},',
- )
- emitter.emit_line("{NULL} /* Sentinel */")
- emitter.emit_line("};")
- def generate_side_table_for_class(
- cl: ClassIR, name: str, type: str, slots: dict[str, str], emitter: Emitter
- ) -> str | None:
- name = f"{cl.name_prefix(emitter.names)}_{name}"
- emitter.emit_line(f"static {type} {name} = {{")
- for field, value in slots.items():
- emitter.emit_line(f".{field} = {value},")
- emitter.emit_line("};")
- return name
- def generate_getseter_declarations(cl: ClassIR, emitter: Emitter) -> None:
- if not cl.is_trait:
- for attr in cl.attributes:
- emitter.emit_line("static PyObject *")
- emitter.emit_line(
- "{}({} *self, void *closure);".format(
- getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- emitter.emit_line("static int")
- emitter.emit_line(
- "{}({} *self, PyObject *value, void *closure);".format(
- setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- for prop, (getter, setter) in cl.properties.items():
- if getter.decl.implicit:
- continue
- # Generate getter declaration
- emitter.emit_line("static PyObject *")
- emitter.emit_line(
- "{}({} *self, void *closure);".format(
- getter_name(cl, prop, emitter.names), cl.struct_name(emitter.names)
- )
- )
- # Generate property setter declaration if a setter exists
- if setter:
- emitter.emit_line("static int")
- emitter.emit_line(
- "{}({} *self, PyObject *value, void *closure);".format(
- setter_name(cl, prop, emitter.names), cl.struct_name(emitter.names)
- )
- )
- def generate_getseters_table(cl: ClassIR, name: str, emitter: Emitter) -> None:
- emitter.emit_line(f"static PyGetSetDef {name}[] = {{")
- if not cl.is_trait:
- for attr in cl.attributes:
- emitter.emit_line(f'{{"{attr}",')
- emitter.emit_line(
- " (getter){}, (setter){},".format(
- getter_name(cl, attr, emitter.names), setter_name(cl, attr, emitter.names)
- )
- )
- emitter.emit_line(" NULL, NULL},")
- for prop, (getter, setter) in cl.properties.items():
- if getter.decl.implicit:
- continue
- emitter.emit_line(f'{{"{prop}",')
- emitter.emit_line(f" (getter){getter_name(cl, prop, emitter.names)},")
- if setter:
- emitter.emit_line(f" (setter){setter_name(cl, prop, emitter.names)},")
- emitter.emit_line("NULL, NULL},")
- else:
- emitter.emit_line("NULL, NULL, NULL},")
- emitter.emit_line("{NULL} /* Sentinel */")
- emitter.emit_line("};")
- def generate_getseters(cl: ClassIR, emitter: Emitter) -> None:
- if not cl.is_trait:
- for i, (attr, rtype) in enumerate(cl.attributes.items()):
- generate_getter(cl, attr, rtype, emitter)
- emitter.emit_line("")
- generate_setter(cl, attr, rtype, emitter)
- if i < len(cl.attributes) - 1:
- emitter.emit_line("")
- for prop, (getter, setter) in cl.properties.items():
- if getter.decl.implicit:
- continue
- rtype = getter.sig.ret_type
- emitter.emit_line("")
- generate_readonly_getter(cl, prop, rtype, getter, emitter)
- if setter:
- arg_type = setter.sig.args[1].type
- emitter.emit_line("")
- generate_property_setter(cl, prop, arg_type, setter, emitter)
- def generate_getter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None:
- attr_field = emitter.attr(attr)
- emitter.emit_line("static PyObject *")
- emitter.emit_line(
- "{}({} *self, void *closure)".format(
- getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- emitter.emit_line("{")
- attr_expr = f"self->{attr_field}"
- # HACK: Don't consider refcounted values as always defined, since it's possible to
- # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted
- # values is benign.
- 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)
- 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;")
- emitter.emit_line("}")
- def generate_setter(cl: ClassIR, attr: str, rtype: RType, emitter: Emitter) -> None:
- attr_field = emitter.attr(attr)
- emitter.emit_line("static int")
- emitter.emit_line(
- "{}({} *self, PyObject *value, void *closure)".format(
- setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- emitter.emit_line("{")
- deletable = cl.is_deletable(attr)
- if not deletable:
- emitter.emit_line("if (value == NULL) {")
- emitter.emit_line("PyErr_SetString(PyExc_AttributeError,")
- emitter.emit_line(
- f' "{repr(cl.name)} object attribute {repr(attr)} cannot be deleted");'
- )
- emitter.emit_line("return -1;")
- emitter.emit_line("}")
- # HACK: Don't consider refcounted values as always defined, since it's possible to
- # access uninitialized values via 'gc.get_objects()'. Accessing non-refcounted
- # values is benign.
- always_defined = cl.is_always_defined(attr) and not rtype.is_refcounted
- 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)
- emitter.emit_dec_ref(f"self->{attr_field}", rtype)
- if not always_defined:
- emitter.emit_line("}")
- if deletable:
- emitter.emit_line("if (value != NULL) {")
- if rtype.is_unboxed:
- emitter.emit_unbox("value", "tmp", rtype, error=ReturnHandler("-1"), declare_dest=True)
- elif is_same_type(rtype, object_rprimitive):
- emitter.emit_line("PyObject *tmp = value;")
- else:
- emitter.emit_cast("value", "tmp", rtype, declare_dest=True)
- emitter.emit_lines("if (!tmp)", " return -1;")
- 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)
- 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_line("return 0;")
- emitter.emit_line("}")
- def generate_readonly_getter(
- cl: ClassIR, attr: str, rtype: RType, func_ir: FuncIR, emitter: Emitter
- ) -> None:
- emitter.emit_line("static PyObject *")
- emitter.emit_line(
- "{}({} *self, void *closure)".format(
- getter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- emitter.emit_line("{")
- if rtype.is_unboxed:
- emitter.emit_line(
- "{}retval = {}{}((PyObject *) self);".format(
- emitter.ctype_spaced(rtype), NATIVE_PREFIX, func_ir.cname(emitter.names)
- )
- )
- 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("}")
- def generate_property_setter(
- cl: ClassIR, attr: str, arg_type: RType, func_ir: FuncIR, emitter: Emitter
- ) -> None:
- emitter.emit_line("static int")
- emitter.emit_line(
- "{}({} *self, PyObject *value, void *closure)".format(
- setter_name(cl, attr, emitter.names), cl.struct_name(emitter.names)
- )
- )
- emitter.emit_line("{")
- if arg_type.is_unboxed:
- emitter.emit_unbox("value", "tmp", arg_type, error=ReturnHandler("-1"), declare_dest=True)
- emitter.emit_line(
- f"{NATIVE_PREFIX}{func_ir.cname(emitter.names)}((PyObject *) self, tmp);"
- )
- else:
- emitter.emit_line(
- f"{NATIVE_PREFIX}{func_ir.cname(emitter.names)}((PyObject *) self, value);"
- )
- emitter.emit_line("return 0;")
- emitter.emit_line("}")
- def has_managed_dict(cl: ClassIR, emitter: Emitter) -> bool:
- """Should the class get the Py_TPFLAGS_MANAGED_DICT flag?"""
- # On 3.11 and earlier the flag doesn't exist and we use
- # tp_dictoffset instead. If a class inherits from Exception, the
- # flag conflicts with tp_dictoffset set in the base class.
- return (
- emitter.capi_version >= (3, 12)
- and cl.has_dict
- and cl.builtin_base != "PyBaseExceptionObject"
- )
|