def generate_richcompare_wrapper(cl: ClassIR, emitter: Emitter) -> Optional[str]: """Generates a wrapper for richcompare dunder methods.""" matches = [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_trait_vtable_setup(cl: ClassIR, vtable_setup_name: str, vtable_name: str, emitter: Emitter) -> None: """Generate a native function that fixes up the trait vtables of a class. This needs to be called before a class is used. """ emitter.emit_line('static bool') emitter.emit_line('{}{}(void)'.format(NATIVE_PREFIX, vtable_setup_name)) emitter.emit_line('{') if cl.trait_vtables and not cl.is_trait: emitter.emit_lines('CPy_FixupTraitVtable({}_vtable, {});'.format( cl.name_prefix(emitter.names), len(cl.trait_vtables))) emitter.emit_line('return 1;') emitter.emit_line('}')
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_vtables(base: ClassIR, vtable_name: str, emitter: Emitter) -> str: """Emit the vtables for a class. This includes both the primary vtable and any trait implementation vtables. Returns the expression to use to refer to the vtable, which might be different than the name, if there are trait vtables.""" subtables = [] for trait, vtable in base.trait_vtables.items(): name = '{}_{}_trait_vtable'.format(base.name_prefix(emitter.names), trait.name_prefix(emitter.names)) generate_vtable(vtable, name, emitter, []) subtables.append((trait, name)) generate_vtable(base.vtable_entries, vtable_name, emitter, subtables) return vtable_name if not subtables else "{} + {}".format( vtable_name, len(subtables) * 2)
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), 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_AsLongLong(retval);') else: emitter.emit_line('Py_ssize_t val = PyLong_AsLongLong(retval);') 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 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) 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() generate_object_struct(cl, emitter) 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. if any(x.inherits_python for x in cl.mro): fields['tp_basicsize'] = '{} + 2*sizeof(PyObject *)'.format(base_size) fields['tp_dictoffset'] = base_size fields['tp_weaklistoffset'] = '{} + sizeof(PyObject *)'.format( base_size) else: fields['tp_basicsize'] = base_size if generate_full: emitter.emit_line('static PyObject *{}(void);'.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) vtable_name = generate_vtables(cl, vtable_name, emitter) 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(&PyType_Type, 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() generate_trait_vtable_setup(cl, vtable_setup_name, vtable_name, emitter) if generate_full: generate_setup_for_class(cl, setup_name, defaults_fn, 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 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)
def trait_vtable_name(trait: ClassIR) -> str: return '{}_{}_trait_vtable'.format( base.name_prefix(emitter.names), trait.name_prefix(emitter.names))
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) fullname = '{}.{}'.format(module, name) setup_name = new_name = clear_name = dealloc_name = '0' traverse_name = getseters_name = vtable_name = '0' if not cl.is_trait: setup_name = '{}_setup'.format(name_prefix) new_name = '{}_new'.format(name_prefix) traverse_name = '{}_traverse'.format(name_prefix) clear_name = '{}_clear'.format(name_prefix) dealloc_name = '{}_dealloc'.format(name_prefix) getseters_name = '{}_getseters'.format(name_prefix) vtable_name = '{}_vtable'.format(name_prefix) methods_name = '{}_methods'.format(name_prefix) base_arg = "&{}".format( emitter.type_struct_name(cl.base)) if cl.base and not cl.traits else "0" def emit_line() -> None: emitter.emit_line() emit_line() generate_object_struct(cl, emitter) emit_line() # If there is a __init__ method, generate a function for tp_init and # extract the args (which we'll use for the native constructor) init_fn = cl.get_method('__init__') if init_fn: init_name = '{}_init'.format(name_prefix) init_args = init_fn.args[1:] generate_init_for_class(cl, init_name, init_fn, emitter) else: init_name = '0' init_args = [] call_fn = cl.get_method('__call__') call_name = '{}{}'.format(PREFIX, call_fn.cname(emitter.names)) if call_fn else '0' if not cl.is_trait: emitter.emit_line('static PyObject *{}(void);'.format(setup_name)) # TODO: Use RInstance ctor = FuncIR(cl.name, None, module, FuncSignature(init_args, object_rprimitive), [], Environment()) emitter.emit_line(native_function_header(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) vtable_name = generate_vtables(cl, vtable_name, emitter) emit_line() 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() emitter.emit_line(textwrap.dedent("""\ static PyTypeObject {type_struct} = {{ PyVarObject_HEAD_INIT(NULL, 0) "{fullname}", /* tp_name */ sizeof({struct_name}), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor){dealloc_name}, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ {tp_call}, /* tp_call */ 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ (traverseproc){traverse_name}, /* tp_traverse */ (inquiry){clear_name}, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ {methods_name}, /* tp_methods */ 0, /* tp_members */ {getseters_name}, /* tp_getset */ {base_arg}, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ {init_name}, /* tp_init */ 0, /* tp_alloc */ {new_name}, /* tp_new */ }};\ """).format(type_struct=emitter.type_struct_name(cl), struct_name=cl.struct_name(emitter.names), fullname=fullname, traverse_name=traverse_name, clear_name=clear_name, dealloc_name=dealloc_name, tp_call=call_name, new_name=new_name, methods_name=methods_name, getseters_name=getseters_name, init_name=init_name, base_arg=base_arg, )) emitter.emit_line() if not cl.is_trait: generate_setup_for_class(cl, setup_name, vtable_name, emitter) emitter.emit_line() generate_constructor_for_class(cl, ctor, init_fn, setup_name, vtable_name, emitter) emitter.emit_line() generate_getseters(cl, emitter)