usse/funda-scraper/venv/lib/python3.10/site-packages/mypyc/codegen/emitclass.py

987 lines
40 KiB
Python

"""Code generation for native classes and related wrappers."""
from typing import Optional, List, Tuple, Dict, Callable, Mapping, Set
from mypy.backports import OrderedDict
from mypyc.common import PREFIX, NATIVE_PREFIX, REG_PREFIX, use_fastcall
from mypyc.codegen.emit import Emitter, HeaderDeclaration, ReturnHandler
from mypyc.codegen.emitfunc import native_function_header
from mypyc.codegen.emitwrapper import (
generate_dunder_wrapper, generate_hash_wrapper, generate_richcompare_wrapper,
generate_bool_wrapper, generate_get_wrapper, generate_len_wrapper,
generate_set_del_item_wrapper, generate_contains_wrapper, generate_bin_op_wrapper
)
from mypyc.ir.rtypes import RType, RTuple, object_rprimitive
from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD
from mypyc.ir.class_ir import ClassIR, VTableEntries
from mypyc.sametype import is_same_type
from mypyc.namegen import NameGenerator
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 = {
'__bool__': ('nb_bool', generate_bool_wrapper),
'__neg__': ('nb_negative', generate_dunder_wrapper),
'__invert__': ('nb_invert', generate_dunder_wrapper),
'__int__': ('nb_int', generate_dunder_wrapper),
'__float__': ('nb_float', generate_dunder_wrapper),
'__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),
'__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),
'__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),
}
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] = OrderedDict()
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] = OrderedDict()
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:
# __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:
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: Optional[str] = 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')
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;')
for base in reversed(cl.base_mro):
if not base.is_trait:
for attr, rtype in base.attributes.items():
if (attr, rtype) not in seen_attrs:
lines.append('{}{};'.format(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('static CPyVTableItem {}[{}];'.format(
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('offsetof({}, {}),'.format(
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: Optional[FuncIR],
vtable_name: str,
shadow_vtable_name: Optional[str],
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('self = ({struct} *)type->tp_alloc(type, 0);'.format(
struct=cl.struct_name(emitter.names)))
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};')
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():
emitter.emit_line(r'self->{} = {};'.format(
emitter.attr(attr), emitter.c_undefined_value(rtype)))
# 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: Optional[FuncIR],
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(
'int res = {}->tp_init({});'.format(
emitter.type_struct_name(cl),
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: Optional[FuncIR],
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('{}({} *self, visitproc visit, void *arg)'.format(
func_name,
cl.struct_name(emitter.names)))
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 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)
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 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)
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) -> Optional[str]:
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 in cl.properties:
# 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 cl.properties[prop][1]:
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 in cl.properties:
emitter.emit_line(f'{{"{prop}",')
emitter.emit_line(f' (getter){getter_name(cl, prop, emitter.names)},')
setter = cl.properties[prop][1]
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():
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, '==', unlikely=True)
emitter.emit_line('PyErr_SetString(PyExc_AttributeError,')
emitter.emit_line(' "attribute {} of {} undefined");'.format(repr(attr),
repr(cl.name)))
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(' "{} object attribute {} cannot be deleted");'.format(repr(cl.name),
repr(attr)))
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, '!=')
emitter.emit_dec_ref('self->{}'.format(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 deletable:
emitter.emit_line('} else')
emitter.emit_line(' self->{} = {};'.format(attr_field,
emitter.c_undefined_value(rtype)))
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_box('retval', 'retbox', rtype, declare_dest=True)
emitter.emit_line('return retbox;')
else:
emitter.emit_line('return {}{}((PyObject *) self);'.format(NATIVE_PREFIX,
func_ir.cname(emitter.names)))
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('{}{}((PyObject *) self, tmp);'.format(
NATIVE_PREFIX,
func_ir.cname(emitter.names)))
else:
emitter.emit_line('{}{}((PyObject *) self, value);'.format(
NATIVE_PREFIX,
func_ir.cname(emitter.names)))
emitter.emit_line('return 0;')
emitter.emit_line('}')