def generate_richcompare_wrapper(cl: ClassIR, emitter: Emitter) -> Optional[str]: """Generates a wrapper for richcompare dunder methods.""" # Sort for determinism on Python 3.5 matches = sorted([name for name in RICHCOMPARE_OPS if cl.has_method(name)]) if not matches: return None name = '{}_RichCompare_{}'.format(DUNDER_PREFIX, cl.name_prefix(emitter.names)) emitter.emit_line( 'static PyObject *{name}(PyObject *obj_lhs, PyObject *obj_rhs, int op) {{'.format( name=name) ) emitter.emit_line('switch (op) {') for func in matches: emitter.emit_line('case {}: {{'.format(RICHCOMPARE_OPS[func])) method = cl.get_method(func) assert method is not None generate_wrapper_core(method, emitter, arg_names=['lhs', 'rhs']) emitter.emit_line('}') emitter.emit_line('}') emitter.emit_line('Py_INCREF(Py_NotImplemented);') emitter.emit_line('return Py_NotImplemented;') emitter.emit_line('}') return name
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_get_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for native __get__ methods.""" name = '{}{}{}'.format(DUNDER_PREFIX, fn.name, cl.name_prefix(emitter.names)) emitter.emit_line( 'static PyObject *{name}(PyObject *self, PyObject *instance, PyObject *owner) {{'. format(name=name)) emitter.emit_line('instance = instance ? instance : Py_None;') emitter.emit_line('return {}{}(self, instance, owner);'.format( NATIVE_PREFIX, fn.cname(emitter.names))) emitter.emit_line('}') return name
def generate_dunder_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for native __dunder__ methods to be able to fit into the mapping protocol slot. This specifically means that the arguments are taken as *PyObjects and returned as *PyObjects. """ input_args = ', '.join('PyObject *obj_{}'.format(arg.name) for arg in fn.args) name = '{}{}{}'.format(DUNDER_PREFIX, fn.name, cl.name_prefix(emitter.names)) emitter.emit_line('static PyObject *{name}({input_args}) {{'.format( name=name, input_args=input_args, )) generate_wrapper_core(fn, emitter) emitter.emit_line('}') return name
def generate_bool_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for native __bool__ methods.""" name = '{}{}{}'.format(DUNDER_PREFIX, fn.name, cl.name_prefix(emitter.names)) emitter.emit_line('static int {name}(PyObject *self) {{'.format( name=name )) emitter.emit_line('{}val = {}{}(self);'.format(emitter.ctype_spaced(fn.ret_type), NATIVE_PREFIX, fn.cname(emitter.names))) emitter.emit_error_check('val', fn.ret_type, 'return -1;') # This wouldn't be that hard to fix but it seems unimportant and # getting error handling and unboxing right would be fiddly. (And # way easier to do in IR!) assert is_bool_rprimitive(fn.ret_type), "Only bool return supported for __bool__" emitter.emit_line('return val;') emitter.emit_line('}') return name
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_hash_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: """Generates a wrapper for native __hash__ methods.""" name = '{}{}{}'.format(DUNDER_PREFIX, fn.name, cl.name_prefix(emitter.names)) emitter.emit_line('static Py_ssize_t {name}(PyObject *self) {{'.format( name=name )) emitter.emit_line('{}retval = {}{}{}(self);'.format(emitter.ctype_spaced(fn.ret_type), emitter.get_group_prefix(fn.decl), NATIVE_PREFIX, fn.cname(emitter.names))) emitter.emit_error_check('retval', fn.ret_type, 'return -1;') if is_int_rprimitive(fn.ret_type): emitter.emit_line('Py_ssize_t val = CPyTagged_AsSsize_t(retval);') else: emitter.emit_line('Py_ssize_t val = PyLong_AsSsize_t(retval);') emitter.emit_dec_ref('retval', fn.ret_type) emitter.emit_line('if (PyErr_Occurred()) return -1;') # We can't return -1 from a hash function.. emitter.emit_line('if (val == -1) return -2;') emitter.emit_line('return val;') emitter.emit_line('}') return name
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 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 generate_full: fields['tp_new'] = new_name 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() generate_native_getters_and_setters(cl, emitter) 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() 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') 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)