blob: 7c21ac59c0a69c98c72cc683b801131eb110704f [file] [log] [blame] [edit]
"""Code generation for native classes and related wrappers."""
from typing import Optional, List, Tuple, Dict, Callable, Mapping, Set
from mypy.ordered_dict import OrderedDict
from mypyc.common import PREFIX, NATIVE_PREFIX, REG_PREFIX, USE_FASTCALL, USE_VECTORCALL
from mypyc.codegen.emit import Emitter, HeaderDeclaration
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,
)
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 '{}{}'.format(NATIVE_PREFIX, fn.cname(emitter.names))
def wrapper_slot(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str:
return '{}{}'.format(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 = {
'__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),
} # type: SlotTable
AS_MAPPING_SLOT_DEFS = {
'__getitem__': ('mp_subscript', generate_dunder_wrapper),
} # type: SlotTable
AS_NUMBER_SLOT_DEFS = {
'__bool__': ('nb_bool', generate_bool_wrapper),
} # type: SlotTable
AS_ASYNC_SLOT_DEFS = {
'__await__': ('am_await', native_slot),
'__aiter__': ('am_aiter', native_slot),
'__anext__': ('am_anext', native_slot),
} # type: SlotTable
SIDE_TABLES = [
('as_mapping', 'PyMappingMethods', AS_MAPPING_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 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 generate_slots(cl: ClassIR, table: SlotTable, emitter: Emitter) -> Dict[str, str]:
fields = OrderedDict() # type: Dict[str, str]
# Sort for determinism on Python 3.5
for name, (slot, generator) in sorted(table.items()):
method_cls = cl.get_method_and_class(name)
if method_cls and (method_cls[1] == cl or name in ALWAYS_FILL):
fields[slot] = generator(cl, method_cls[0], emitter)
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(
'PyTypeObject *{};'.format(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(
'{};'.format(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 = '{}_setup'.format(name_prefix)
new_name = '{}_new'.format(name_prefix)
members_name = '{}_members'.format(name_prefix)
getseters_name = '{}_getseters'.format(name_prefix)
vtable_name = '{}_vtable'.format(name_prefix)
traverse_name = '{}_traverse'.format(name_prefix)
clear_name = '{}_clear'.format(name_prefix)
dealloc_name = '{}_dealloc'.format(name_prefix)
methods_name = '{}_methods'.format(name_prefix)
vtable_setup_name = '{}_trait_vtable_setup'.format(name_prefix)
fields = OrderedDict() # type: Dict[str, str]
fields['tp_name'] = '"{}"'.format(name)
generate_full = not cl.is_trait and not cl.builtin_base
needs_getseters = not cl.is_generated
if not cl.builtin_base:
fields['tp_new'] = new_name
if generate_full:
fields['tp_dealloc'] = '(destructor){}_dealloc'.format(name_prefix)
fields['tp_traverse'] = '(traverseproc){}_traverse'.format(name_prefix)
fields['tp_clear'] = '(inquiry){}_clear'.format(name_prefix)
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['tp_{}'.format(table_name)] = '&{}'.format(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 = 'sizeof({})'.format(cl.builtin_base)
elif cl.is_trait:
base_size = 'sizeof(PyObject)'
else:
base_size = 'sizeof({})'.format(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 = '{} + sizeof(PyObject *)'.format(base_size)
emitter.emit_lines(
'PyMemberDef {}[] = {{'.format(members_name),
'{{"__dict__", T_OBJECT_EX, {}, 0, NULL}},'.format(base_size),
'{{"__weakref__", T_OBJECT_EX, {}, 0, NULL}},'.format(weak_offset),
'{0}',
'};',
)
fields['tp_members'] = members_name
fields['tp_basicsize'] = '{} + 2*sizeof(PyObject *)'.format(base_size)
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('static PyObject *{}(PyTypeObject *type);'.format(setup_name))
assert cl.ctor is not None
emitter.emit_line(native_function_header(cl.ctor, emitter) + ';')
emit_line()
generate_new_for_class(cl, new_name, vtable_name, setup_name, 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 = generate_vtables(
cl, vtable_setup_name + "_shadow", vtable_name + "_shadow", emitter, shadow=True
) # type: Optional[str]
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 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("static PyTypeObject {}_template_ = {{".format(emitter.type_struct_name(cl)))
emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)")
for field, value in fields.items():
emitter.emit_line(".{} = {},".format(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, '{}_get{}'.format(cl.name, attribute))
def setter_name(cl: ClassIR, attribute: str, names: NameGenerator) -> str:
return names.private_name(cl.module_name, '{}_set{}'.format(cl.name, attribute))
def generate_object_struct(cl: ClassIR, emitter: Emitter) -> None:
seen_attrs = set() # type: Set[Tuple[str, RType]]
lines = [] # type: List[str]
lines += ['typedef struct {',
'PyObject_HEAD',
'CPyVTableItem *vtable;']
if cl.has_method('__call__') and 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('}} {};'.format(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('{}{}(void)'.format(NATIVE_PREFIX, vtable_setup_name))
emitter.emit_line('{')
if base.allow_interpreted_subclasses and not shadow:
emitter.emit_line('{}{}_shadow();'.format(NATIVE_PREFIX, vtable_setup_name))
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 "{} + {}".format(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('size_t {}_scratch[] = {{'.format(trait_offset_table_name))
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('CPyVTableItem {}_scratch[] = {{'.format(vtable_name))
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('{}(PyTypeObject *type)'.format(func_name))
emitter.emit_line('{')
emitter.emit_line('{} *self;'.format(cl.struct_name(emitter.names)))
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('if (type != {}) {{'.format(emitter.type_struct_name(cl)))
emitter.emit_line('self->vtable = {};'.format(shadow_vtable_name))
emitter.emit_line('} else {')
emitter.emit_line('self->vtable = {};'.format(vtable_name))
emitter.emit_line('}')
else:
emitter.emit_line('self->vtable = {};'.format(vtable_name))
if cl.has_method('__call__') and USE_VECTORCALL:
name = cl.method_decl('__call__').cname(emitter.names)
emitter.emit_line('self->vectorcall = {}{};'.format(PREFIX, name))
for base in reversed(cl.base_mro):
for attr, rtype in base.attributes.items():
emitter.emit_line('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('{}'.format(native_function_header(fn, emitter)))
emitter.emit_line('{')
emitter.emit_line('PyObject *self = {}({});'.format(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 = '{}_init'.format(cl.name_prefix(emitter.names))
emitter.emit_line('static int')
emitter.emit_line(
'{}(PyObject *self, PyObject *args, PyObject *kwds)'.format(func_name))
emitter.emit_line('{')
emitter.emit_line('return {}{}(self, args, kwds) != NULL ? 0 : -1;'.format(
PREFIX, init_fn.cname(emitter.names)))
emitter.emit_line('}')
return func_name
def generate_new_for_class(cl: ClassIR,
func_name: str,
vtable_name: str,
setup_name: str,
emitter: Emitter) -> None:
emitter.emit_line('static PyObject *')
emitter.emit_line(
'{}(PyTypeObject *type, PyObject *args, PyObject *kwds)'.format(func_name))
emitter.emit_line('{')
# TODO: Check and unbox arguments
if not cl.allow_interpreted_subclasses:
emitter.emit_line('if (type != {}) {{'.format(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('}')
emitter.emit_line('return {}(type);'.format(setup_name))
emitter.emit_line('}')
def generate_new_for_trait(cl: ClassIR,
func_name: str,
emitter: Emitter) -> None:
emitter.emit_line('static PyObject *')
emitter.emit_line(
'{}(PyTypeObject *type, PyObject *args, PyObject *kwds)'.format(func_name))
emitter.emit_line('{')
emitter.emit_line('if (type != {}) {{'.format(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('self->{}'.format(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('{}({} *self)'.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_clear('self->{}'.format(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('{}({} *self)'.format(dealloc_func_name, cl.struct_name(emitter.names)))
emitter.emit_line('{')
emitter.emit_line('PyObject_GC_UnTrack(self);')
# The trashcan is needed to handle deep recursive deallocations
emitter.emit_line('CPy_TRASHCAN_BEGIN(self, {})'.format(dealloc_func_name))
emitter.emit_line('{}(self);'.format(clear_func_name))
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('static PyMethodDef {}[] = {{'.format(name))
for fn in cl.methods.values():
if fn.decl.is_prop_setter or fn.decl.is_prop_getter:
continue
emitter.emit_line('{{"{}",'.format(fn.name))
emitter.emit_line(' (PyCFunction){}{},'.format(PREFIX, fn.cname(emitter.names)))
if USE_FASTCALL:
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 = '{}_{}'.format(cl.name_prefix(emitter.names), name)
emitter.emit_line('static {} {} = {{'.format(type, name))
for field, value in slots.items():
emitter.emit_line(".{} = {},".format(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('static PyGetSetDef {}[] = {{'.format(name))
if not cl.is_trait:
for attr in cl.attributes:
emitter.emit_line('{{"{}",'.format(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('{{"{}",'.format(prop))
emitter.emit_line(' (getter){},'.format(getter_name(cl, prop, emitter.names)))
setter = cl.properties[prop][1]
if setter:
emitter.emit_line(' (setter){},'.format(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 = 'self->{}'.format(attr_field)
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('self->{}'.format(attr_field), rtype)
emitter.emit_box('self->{}'.format(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('{')
if rtype.is_refcounted:
attr_expr = 'self->{}'.format(attr_field)
emitter.emit_undefined_attr_check(rtype, attr_expr, '!=')
emitter.emit_dec_ref('self->{}'.format(attr_field), rtype)
emitter.emit_line('}')
emitter.emit_line('if (value != NULL) {')
if rtype.is_unboxed:
emitter.emit_unbox('value', 'tmp', rtype, custom_failure='return -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('self->{} = tmp;'.format(attr_field))
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, custom_failure='return -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('}')