Ejemplo n.º 1
0
    def generate_globals_init(self, emitter: Emitter) -> None:
        emitter.emit_lines(
            '',
            'int CPyGlobalsInit(void)',
            '{',
            'static int is_initialized = 0;',
            'if (is_initialized) return 0;',
            ''
        )

        emitter.emit_line('CPy_Init();')
        for symbol, fixup in self.simple_inits:
            emitter.emit_line('{} = {};'.format(symbol, fixup))

        for (_, literal), identifier in self.literals.items():
            symbol = emitter.static_name(identifier, None)
            if isinstance(literal, int):
                actual_symbol = symbol
                symbol = INT_PREFIX + symbol
                emitter.emit_line(
                    'PyObject * {} = PyLong_FromString(\"{}\", NULL, 10);'.format(
                        symbol, str(literal))
                )
            elif isinstance(literal, float):
                emitter.emit_line(
                    '{} = PyFloat_FromDouble({});'.format(symbol, str(literal))
                )
            elif isinstance(literal, complex):
                emitter.emit_line(
                    '{} = PyComplex_FromDoubles({}, {});'.format(
                        symbol, str(literal.real), str(literal.imag))
                )
            elif isinstance(literal, str):
                emitter.emit_line(
                    '{} = PyUnicode_FromStringAndSize({}, {});'.format(
                        symbol, *encode_as_c_string(literal))
                )
            elif isinstance(literal, bytes):
                emitter.emit_line(
                    '{} = PyBytes_FromStringAndSize({}, {});'.format(
                        symbol, *encode_bytes_as_c_string(literal))
                )
            else:
                assert False, ('Literals must be integers, floating point numbers, or strings,',
                               'but the provided literal is of type {}'.format(type(literal)))
            emitter.emit_lines('if (unlikely({} == NULL))'.format(symbol),
                               '    return -1;')
            # Ints have an unboxed representation.
            if isinstance(literal, int):
                emitter.emit_line(
                    '{} = CPyTagged_FromObject({});'.format(actual_symbol, symbol)
                )

        emitter.emit_lines(
            'is_initialized = 1;',
            'return 0;',
            '}',
        )
Ejemplo n.º 2
0
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))

    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('}')
Ejemplo n.º 3
0
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('}')
Ejemplo n.º 4
0
def generate_wrapper_core(fn: FuncIR, emitter: Emitter,
                          optional_args: Optional[List[RuntimeArg]] = None,
                          arg_names: Optional[List[str]] = None,
                          cleanups: Optional[List[str]] = None,
                          traceback_code: Optional[str] = None) -> None:
    """Generates the core part of a wrapper function for a native function.
    This expects each argument as a PyObject * named obj_{arg} as a precondition.
    It converts the PyObject *s to the necessary types, checking and unboxing if necessary,
    makes the call, then boxes the result if necessary and returns it.
    """

    optional_args = optional_args or []
    cleanups = cleanups or []
    use_goto = bool(cleanups or traceback_code)
    error_code = 'return NULL;' if not use_goto else 'goto fail;'

    arg_names = arg_names or [arg.name for arg in fn.args]
    for arg_name, arg in zip(arg_names, fn.args):
        # Suppress the argument check for *args/**kwargs, since we know it must be right.
        typ = arg.type if arg.kind not in (ARG_STAR, ARG_STAR2) else object_rprimitive
        generate_arg_check(arg_name, typ, emitter, error_code, arg in optional_args)
    native_args = ', '.join('arg_{}'.format(arg) for arg in arg_names)
    if fn.ret_type.is_unboxed or use_goto:
        # TODO: The Py_RETURN macros return the correct PyObject * with reference count handling.
        #       Are they relevant?
        emitter.emit_line('{}retval = {}{}({});'.format(emitter.ctype_spaced(fn.ret_type),
                                                        NATIVE_PREFIX,
                                                        fn.cname(emitter.names),
                                                        native_args))
        emitter.emit_lines(*cleanups)
        if fn.ret_type.is_unboxed:
            emitter.emit_error_check('retval', fn.ret_type, 'return NULL;')
            emitter.emit_box('retval', 'retbox', fn.ret_type, declare_dest=True)

        emitter.emit_line('return {};'.format('retbox' if fn.ret_type.is_unboxed else 'retval'))
    else:
        emitter.emit_line('return {}{}({});'.format(NATIVE_PREFIX,
                                                    fn.cname(emitter.names),
                                                    native_args))
        # TODO: Tracebacks?

    if use_goto:
        emitter.emit_label('fail')
        emitter.emit_lines(*cleanups)
        if traceback_code:
            emitter.emit_lines(traceback_code)
        emitter.emit_lines('return NULL;')
Ejemplo n.º 5
0
    def generate_globals_init(self, emitter: Emitter) -> None:
        emitter.emit_lines('', 'int CPyGlobalsInit(void)', '{',
                           'static int is_initialized = 0;',
                           'if (is_initialized) return 0;', '')

        emitter.emit_line('CPy_Init();')
        for symbol, fixup in self.simple_inits:
            emitter.emit_line(f'{symbol} = {fixup};')

        values = 'CPyLit_Str, CPyLit_Bytes, CPyLit_Int, CPyLit_Float, CPyLit_Complex, CPyLit_Tuple'
        emitter.emit_lines(
            f'if (CPyStatics_Initialize(CPyStatics, {values}) < 0) {{',
            'return -1;', '}')

        emitter.emit_lines(
            'is_initialized = 1;',
            'return 0;',
            '}',
        )
Ejemplo n.º 6
0
    def generate_module_def(self, emitter: Emitter, module_name: str,
                            module: ModuleIR) -> None:
        """Emit the PyModuleDef struct for a module and the module init function."""
        # Emit module methods
        module_prefix = emitter.names.private_name(module_name)
        emitter.emit_line(
            'static PyMethodDef {}module_methods[] = {{'.format(module_prefix))
        for fn in module.functions:
            if fn.class_name is not None or fn.name == TOP_LEVEL_NAME:
                continue
            emitter.emit_line((
                '{{"{name}", (PyCFunction){prefix}{cname}, METH_VARARGS | METH_KEYWORDS, '
                'NULL /* docstring */}},').format(name=fn.name,
                                                  cname=fn.cname(
                                                      emitter.names),
                                                  prefix=PREFIX))
        emitter.emit_line('{NULL, NULL, 0, NULL}')
        emitter.emit_line('};')
        emitter.emit_line()

        # Emit module definition struct
        emitter.emit_lines(
            'static struct PyModuleDef {}module = {{'.format(module_prefix),
            'PyModuleDef_HEAD_INIT,', '"{}",'.format(module_name),
            'NULL, /* docstring */',
            '-1,       /* size of per-interpreter state of the module,',
            '             or -1 if the module keeps state in global variables. */',
            '{}module_methods'.format(module_prefix), '};')
        emitter.emit_line()
        # Emit module init function. If we are compiling just one module, this
        # will be the C API init function. If we are compiling 2+ modules, we
        # generate a shared library for the modules and shims that call into
        # the shared library, and in this case we use an internal module
        # initialized function that will be called by the shim.
        if not self.use_shared_lib:
            declaration = 'PyMODINIT_FUNC PyInit_{}(void)'.format(module_name)
        else:
            declaration = 'PyObject *CPyInit_{}(void)'.format(
                exported_name(module_name))
        emitter.emit_lines(declaration, '{')
        # Store the module reference in a static and return it when necessary.
        # This is separate from the *global* reference to the module that will
        # be populated when it is imported by a compiled module. We want that
        # reference to only be populated when the module has been successfully
        # imported, whereas this we want to have to stop a circular import.
        module_static = self.module_internal_static_name(module_name, emitter)

        emitter.emit_lines('if ({}) {{'.format(module_static),
                           'Py_INCREF({});'.format(module_static),
                           'return {};'.format(module_static), '}')

        emitter.emit_lines(
            '{} = PyModule_Create(&{}module);'.format(module_static,
                                                      module_prefix),
            'if (unlikely({} == NULL))'.format(module_static),
            '    return NULL;')
        emitter.emit_line(
            'PyObject *modname = PyObject_GetAttrString((PyObject *){}, "__name__");'
            .format(module_static))

        module_globals = emitter.static_name('globals', module_name)
        emitter.emit_lines(
            '{} = PyModule_GetDict({});'.format(module_globals, module_static),
            'if (unlikely({} == NULL))'.format(module_globals),
            '    return NULL;')

        # HACK: Manually instantiate generated classes here
        for cl in module.classes:
            if cl.is_generated:
                type_struct = emitter.type_struct_name(cl)
                emitter.emit_lines(
                    '{t} = (PyTypeObject *)CPyType_FromTemplate({t}_template, NULL, modname);'
                    .format(t=type_struct))
                emitter.emit_lines('if (unlikely(!{}))'.format(type_struct),
                                   '    return NULL;')

        emitter.emit_lines('if (CPyGlobalsInit() < 0)', '    return NULL;')

        self.generate_top_level_call(module, emitter)

        emitter.emit_lines('Py_DECREF(modname);')

        emitter.emit_line('return {};'.format(module_static))
        emitter.emit_line('}')
Ejemplo n.º 7
0
    def generate_shared_lib_init(self, emitter: Emitter) -> None:
        """Generate the init function for a shared library.

        A shared library contains all of the actual code for a
        compilation group.

        The init function is responsible for creating Capsules that
        wrap pointers to the initialization function of all the real
        init functions for modules in this shared library as well as
        the export table containing all of the exported functions and
        values from all the modules.

        These capsules are stored in attributes of the shared library.
        """
        assert self.group_name is not None

        emitter.emit_line()
        emitter.emit_lines(
            'PyMODINIT_FUNC PyInit_{}(void)'.format(
                shared_lib_name(self.group_name).split('.')[-1]),
            '{',
            ('static PyModuleDef def = {{ PyModuleDef_HEAD_INIT, "{}", NULL, -1, NULL, NULL }};'
             .format(shared_lib_name(self.group_name))),
            'int res;',
            'PyObject *capsule;',
            'PyObject *tmp;',
            'static PyObject *module;',
            'if (module) {',
            'Py_INCREF(module);',
            'return module;',
            '}',
            'module = PyModule_Create(&def);',
            'if (!module) {',
            'goto fail;',
            '}',
            '',
        )

        emitter.emit_lines(
            'capsule = PyCapsule_New(&exports, "{}.exports", NULL);'.format(
                shared_lib_name(self.group_name)),
            'if (!capsule) {',
            'goto fail;',
            '}',
            'res = PyObject_SetAttrString(module, "exports", capsule);',
            'Py_DECREF(capsule);',
            'if (res < 0) {',
            'goto fail;',
            '}',
            '',
        )

        for mod, _ in self.modules:
            name = exported_name(mod)
            emitter.emit_lines(
                'extern PyObject *CPyInit_{}(void);'.format(name),
                'capsule = PyCapsule_New((void *)CPyInit_{}, "{}.init_{}", NULL);'
                .format(name, shared_lib_name(self.group_name), name),
                'if (!capsule) {',
                'goto fail;',
                '}',
                'res = PyObject_SetAttrString(module, "init_{}", capsule);'.
                format(name),
                'Py_DECREF(capsule);',
                'if (res < 0) {',
                'goto fail;',
                '}',
                '',
            )

        for group in sorted(self.context.group_deps):
            egroup = exported_name(group)
            emitter.emit_lines(
                'tmp = PyImport_ImportModule("{}"); if (!tmp) goto fail; Py_DECREF(tmp);'
                .format(shared_lib_name(group)),
                'struct export_table_{} *pexports_{} = PyCapsule_Import("{}.exports", 0);'
                .format(egroup, egroup, shared_lib_name(group)),
                'if (!pexports_{}) {{'.format(egroup),
                'goto fail;',
                '}',
                'memcpy(&exports_{group}, pexports_{group}, sizeof(exports_{group}));'
                .format(group=egroup),
                '',
            )

        emitter.emit_lines(
            'return module;',
            'fail:',
            'Py_XDECREF(module);',
            'return NULL;',
            '}',
        )
Ejemplo n.º 8
0
    def generate_export_table(self, decl_emitter: Emitter,
                              code_emitter: Emitter) -> None:
        """Generate the declaration and definition of the group's export struct.

        To avoid needing to deal with deeply platform specific issues
        involving dynamic library linking (and some possibly
        insurmountable issues involving cyclic dependencies), compiled
        code accesses functions and data in other compilation groups
        via an explicit "export struct".

        Each group declares a struct type that contains a pointer to
        every function and static variable it exports. It then
        populates this struct and stores a pointer to it in a capsule
        stored as an attribute named 'exports' on the group's shared
        library's python module.

        On load, a group's init function will import all of its
        dependencies' exports tables using the capsule mechanism and
        copy the contents into a local copy of the table (to eliminate
        the need for a pointer indirection when accessing it).

        Then, all calls to functions in another group and accesses to statics
        from another group are done indirectly via the export table.

        For example, a group containing a module b, where b contains a class B
        and a function bar, would declare an export table like:
            struct export_table_b {
                PyTypeObject **CPyType_B;
                PyObject *(*CPyDef_B)(CPyTagged cpy_r_x);
                CPyTagged (*CPyDef_B___foo)(PyObject *cpy_r_self, CPyTagged cpy_r_y);
                tuple_T2OI (*CPyDef_bar)(PyObject *cpy_r_x);
                char (*CPyDef___top_level__)(void);
            };
        that would be initialized with:
            static struct export_table_b exports = {
                &CPyType_B,
                &CPyDef_B,
                &CPyDef_B___foo,
                &CPyDef_bar,
                &CPyDef___top_level__,
            };
        To call `b.foo`, then, a function in another group would do
        `exports_b.CPyDef_bar(...)`.
        """

        decls = decl_emitter.context.declarations

        decl_emitter.emit_lines(
            '',
            'struct export_table{} {{'.format(self.group_suffix),
        )
        for name, decl in decls.items():
            if decl.needs_export:
                decl_emitter.emit_line(pointerize('\n'.join(decl.decl), name))

        decl_emitter.emit_line('};')

        code_emitter.emit_lines(
            '',
            'static struct export_table{} exports = {{'.format(
                self.group_suffix),
        )
        for name, decl in decls.items():
            if decl.needs_export:
                code_emitter.emit_line('&{},'.format(name))

        code_emitter.emit_line('};')
Ejemplo n.º 9
0
    def generate_c_for_modules(self) -> List[Tuple[str, str]]:
        file_contents = []
        multi_file = self.use_shared_lib and self.multi_file

        base_emitter = Emitter(self.context)
        # Optionally just include the runtime library c files to
        # reduce the number of compiler invocations needed
        if self.compiler_options.include_runtime_files:
            base_emitter.emit_line('#include "CPy.c"')
            base_emitter.emit_line('#include "getargs.c"')
        base_emitter.emit_line('#include "__native{}.h"'.format(
            self.short_group_suffix))
        base_emitter.emit_line('#include "__native_internal{}.h"'.format(
            self.short_group_suffix))
        emitter = base_emitter

        for (_, literal), identifier in self.literals.items():
            if isinstance(literal, int):
                symbol = emitter.static_name(identifier, None)
                self.declare_global('CPyTagged ', symbol)
            else:
                self.declare_static_pyobject(identifier, emitter)

        for module_name, module in self.modules:
            if multi_file:
                emitter = Emitter(self.context)
                emitter.emit_line('#include "__native{}.h"'.format(
                    self.short_group_suffix))
                emitter.emit_line('#include "__native_internal{}.h"'.format(
                    self.short_group_suffix))

            self.declare_module(module_name, emitter)
            self.declare_internal_globals(module_name, emitter)
            self.declare_imports(module.imports, emitter)

            for cl in module.classes:
                if cl.is_ext_class:
                    generate_class(cl, module_name, emitter)

            # Generate Python extension module definitions and module initialization functions.
            self.generate_module_def(emitter, module_name, module)

            for fn in module.functions:
                emitter.emit_line()
                generate_native_function(fn, emitter,
                                         self.source_paths[module_name],
                                         module_name)
                if fn.name != TOP_LEVEL_NAME:
                    emitter.emit_line()
                    generate_wrapper_function(fn, emitter,
                                              self.source_paths[module_name],
                                              module_name)

            if multi_file:
                name = ('__native_{}.c'.format(
                    emitter.names.private_name(module_name)))
                file_contents.append((name, ''.join(emitter.fragments)))

        # The external header file contains type declarations while
        # the internal contains declarations of functions and objects
        # (which are shared between shared libraries via dynamic
        # exports tables and not accessed directly.)
        ext_declarations = Emitter(self.context)
        ext_declarations.emit_line('#ifndef MYPYC_NATIVE{}_H'.format(
            self.group_suffix))
        ext_declarations.emit_line('#define MYPYC_NATIVE{}_H'.format(
            self.group_suffix))
        ext_declarations.emit_line('#include <Python.h>')
        ext_declarations.emit_line('#include <CPy.h>')

        declarations = Emitter(self.context)
        declarations.emit_line('#ifndef MYPYC_NATIVE_INTERNAL{}_H'.format(
            self.group_suffix))
        declarations.emit_line('#define MYPYC_NATIVE_INTERNAL{}_H'.format(
            self.group_suffix))
        declarations.emit_line('#include <Python.h>')
        declarations.emit_line('#include <CPy.h>')
        declarations.emit_line('#include "__native{}.h"'.format(
            self.short_group_suffix))
        declarations.emit_line()
        declarations.emit_line('int CPyGlobalsInit(void);')
        declarations.emit_line()

        for module_name, module in self.modules:
            self.declare_finals(module_name, module.final_names, declarations)
            for cl in module.classes:
                generate_class_type_decl(cl, emitter, ext_declarations,
                                         declarations)
            for fn in module.functions:
                generate_function_declaration(fn, declarations)

        for lib in sorted(self.context.group_deps):
            elib = exported_name(lib)
            short_lib = exported_name(lib.split('.')[-1])
            declarations.emit_lines(
                '#include <{}>'.format(
                    os.path.join(group_dir(lib),
                                 "__native_{}.h".format(short_lib))),
                'struct export_table_{} exports_{};'.format(elib, elib))

        sorted_decls = self.toposort_declarations()

        emitter = base_emitter
        self.generate_globals_init(emitter)

        emitter.emit_line()

        for declaration in sorted_decls:
            decls = ext_declarations if declaration.is_type else declarations
            if not declaration.is_type:
                decls.emit_lines('extern {}'.format(declaration.decl[0]),
                                 *declaration.decl[1:])
                # If there is a definition, emit it. Otherwise repeat the declaration
                # (without an extern).
                if declaration.defn:
                    emitter.emit_lines(*declaration.defn)
                else:
                    emitter.emit_lines(*declaration.decl)
            else:
                decls.emit_lines(*declaration.decl)

        if self.group_name:
            self.generate_export_table(ext_declarations, emitter)

            self.generate_shared_lib_init(emitter)

        ext_declarations.emit_line('#endif')
        declarations.emit_line('#endif')

        output_dir = group_dir(self.group_name) if self.group_name else ''
        return file_contents + [
            (os.path.join(output_dir, '__native{}.c'.format(
                self.short_group_suffix)), ''.join(emitter.fragments)),
            (os.path.join(
                output_dir, '__native_internal{}.h'.format(
                    self.short_group_suffix)), ''.join(
                        declarations.fragments)),
            (os.path.join(output_dir, '__native{}.h'.format(
                self.short_group_suffix)), ''.join(
                    ext_declarations.fragments)),
        ]
Ejemplo n.º 10
0
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: Dict[str, str] = OrderedDict()
    fields['tp_name'] = '"{}"'.format(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'] = '(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: 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("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)
Ejemplo n.º 11
0
def generate_wrapper_function(fn: FuncIR,
                              emitter: Emitter,
                              source_path: str,
                              module_name: str) -> None:
    """Generate a CPython-compatible vectorcall wrapper for a native function.

    In particular, this handles unboxing the arguments, calling the native function, and
    then boxing the return value.
    """
    emitter.emit_line('{} {{'.format(wrapper_function_header(fn, emitter.names)))

    # If fn is a method, then the first argument is a self param
    real_args = list(fn.args)
    if fn.class_name and not fn.decl.kind == FUNC_STATICMETHOD:
        arg = real_args.pop(0)
        emitter.emit_line('PyObject *obj_{} = self;'.format(arg.name))

    # Need to order args as: required, optional, kwonly optional, kwonly required
    # This is because CPyArg_ParseStackAndKeywords format string requires
    # them grouped in that way.
    groups = make_arg_groups(real_args)
    reordered_args = reorder_arg_groups(groups)

    emitter.emit_line(make_static_kwlist(reordered_args))
    fmt = make_format_string(fn.name, groups)
    # Define the arguments the function accepts (but no types yet)
    emitter.emit_line('static CPyArg_Parser parser = {{"{}", kwlist, 0}};'.format(fmt))

    for arg in real_args:
        emitter.emit_line('PyObject *obj_{}{};'.format(
                          arg.name, ' = NULL' if arg.optional else ''))

    cleanups = ['CPy_DECREF(obj_{});'.format(arg.name)
                for arg in groups[ARG_STAR] + groups[ARG_STAR2]]

    arg_ptrs: List[str] = []
    if groups[ARG_STAR] or groups[ARG_STAR2]:
        arg_ptrs += ['&obj_{}'.format(groups[ARG_STAR][0].name) if groups[ARG_STAR] else 'NULL']
        arg_ptrs += ['&obj_{}'.format(groups[ARG_STAR2][0].name) if groups[ARG_STAR2] else 'NULL']
    arg_ptrs += ['&obj_{}'.format(arg.name) for arg in reordered_args]

    if fn.name == '__call__' and use_vectorcall(emitter.capi_version):
        nargs = 'PyVectorcall_NARGS(nargs)'
    else:
        nargs = 'nargs'
    parse_fn = 'CPyArg_ParseStackAndKeywords'
    # Special case some common signatures
    if len(real_args) == 0:
        # No args
        parse_fn = 'CPyArg_ParseStackAndKeywordsNoArgs'
    elif len(real_args) == 1 and len(groups[ARG_POS]) == 1:
        # Single positional arg
        parse_fn = 'CPyArg_ParseStackAndKeywordsOneArg'
    elif len(real_args) == len(groups[ARG_POS]) + len(groups[ARG_OPT]):
        # No keyword-only args, *args or **kwargs
        parse_fn = 'CPyArg_ParseStackAndKeywordsSimple'
    emitter.emit_lines(
        'if (!{}(args, {}, kwnames, &parser{})) {{'.format(
            parse_fn, nargs, ''.join(', ' + n for n in arg_ptrs)),
        'return NULL;',
        '}')
    traceback_code = generate_traceback_code(fn, emitter, source_path, module_name)
    generate_wrapper_core(fn, emitter, groups[ARG_OPT] + groups[ARG_NAMED_OPT],
                          cleanups=cleanups,
                          traceback_code=traceback_code)

    emitter.emit_line('}')
Ejemplo n.º 12
0
    def generate_module_def(self, emitter: Emitter, module_name: str,
                            module: ModuleIR) -> None:
        """Emit the PyModuleDef struct for a module and the module init function."""
        # Emit module methods
        module_prefix = emitter.names.private_name(module_name)
        emitter.emit_line(
            f'static PyMethodDef {module_prefix}module_methods[] = {{')
        for fn in module.functions:
            if fn.class_name is not None or fn.name == TOP_LEVEL_NAME:
                continue
            name = short_id_from_name(fn.name, fn.decl.shortname, fn.line)
            if is_fastcall_supported(fn, emitter.capi_version):
                flag = 'METH_FASTCALL'
            else:
                flag = 'METH_VARARGS'
            emitter.emit_line((
                '{{"{name}", (PyCFunction){prefix}{cname}, {flag} | METH_KEYWORDS, '
                'NULL /* docstring */}},').format(name=name,
                                                  cname=fn.cname(
                                                      emitter.names),
                                                  prefix=PREFIX,
                                                  flag=flag))
        emitter.emit_line('{NULL, NULL, 0, NULL}')
        emitter.emit_line('};')
        emitter.emit_line()

        # Emit module definition struct
        emitter.emit_lines(
            f'static struct PyModuleDef {module_prefix}module = {{',
            'PyModuleDef_HEAD_INIT,', f'"{module_name}",',
            'NULL, /* docstring */',
            '-1,       /* size of per-interpreter state of the module,',
            '             or -1 if the module keeps state in global variables. */',
            f'{module_prefix}module_methods', '};')
        emitter.emit_line()
        # Emit module init function. If we are compiling just one module, this
        # will be the C API init function. If we are compiling 2+ modules, we
        # generate a shared library for the modules and shims that call into
        # the shared library, and in this case we use an internal module
        # initialized function that will be called by the shim.
        if not self.use_shared_lib:
            declaration = f'PyMODINIT_FUNC PyInit_{module_name}(void)'
        else:
            declaration = f'PyObject *CPyInit_{exported_name(module_name)}(void)'
        emitter.emit_lines(declaration, '{')
        emitter.emit_line('PyObject* modname = NULL;')
        # Store the module reference in a static and return it when necessary.
        # This is separate from the *global* reference to the module that will
        # be populated when it is imported by a compiled module. We want that
        # reference to only be populated when the module has been successfully
        # imported, whereas this we want to have to stop a circular import.
        module_static = self.module_internal_static_name(module_name, emitter)

        emitter.emit_lines(f'if ({module_static}) {{',
                           f'Py_INCREF({module_static});',
                           f'return {module_static};', '}')

        emitter.emit_lines(
            f'{module_static} = PyModule_Create(&{module_prefix}module);',
            f'if (unlikely({module_static} == NULL))', '    goto fail;')
        emitter.emit_line(
            'modname = PyObject_GetAttrString((PyObject *){}, "__name__");'.
            format(module_static))

        module_globals = emitter.static_name('globals', module_name)
        emitter.emit_lines(
            f'{module_globals} = PyModule_GetDict({module_static});',
            f'if (unlikely({module_globals} == NULL))', '    goto fail;')

        # HACK: Manually instantiate generated classes here
        type_structs: List[str] = []
        for cl in module.classes:
            type_struct = emitter.type_struct_name(cl)
            type_structs.append(type_struct)
            if cl.is_generated:
                emitter.emit_lines(
                    '{t} = (PyTypeObject *)CPyType_FromTemplate('
                    '(PyObject *){t}_template, NULL, modname);'.format(
                        t=type_struct))
                emitter.emit_lines(f'if (unlikely(!{type_struct}))',
                                   '    goto fail;')

        emitter.emit_lines('if (CPyGlobalsInit() < 0)', '    goto fail;')

        self.generate_top_level_call(module, emitter)

        emitter.emit_lines('Py_DECREF(modname);')

        emitter.emit_line(f'return {module_static};')
        emitter.emit_lines('fail:', f'Py_CLEAR({module_static});',
                           'Py_CLEAR(modname);')
        for name, typ in module.final_names:
            static_name = emitter.static_name(name, module_name)
            emitter.emit_dec_ref(static_name, typ, is_xdec=True)
            undef = emitter.c_undefined_value(typ)
            emitter.emit_line(f'{static_name} = {undef};')
        # the type objects returned from CPyType_FromTemplate are all new references
        # so we have to decref them
        for t in type_structs:
            emitter.emit_line(f'Py_CLEAR({t});')
        emitter.emit_line('return NULL;')
        emitter.emit_line('}')
Ejemplo n.º 13
0
    def generate_c_for_modules(self) -> List[Tuple[str, str]]:
        file_contents = []
        multi_file = self.use_shared_lib and self.multi_file

        # Collect all literal refs in IR.
        for _, module in self.modules:
            for fn in module.functions:
                collect_literals(fn, self.context.literals)

        base_emitter = Emitter(self.context)
        # Optionally just include the runtime library c files to
        # reduce the number of compiler invocations needed
        if self.compiler_options.include_runtime_files:
            for name in RUNTIME_C_FILES:
                base_emitter.emit_line(f'#include "{name}"')
        base_emitter.emit_line(
            f'#include "__native{self.short_group_suffix}.h"')
        base_emitter.emit_line(
            f'#include "__native_internal{self.short_group_suffix}.h"')
        emitter = base_emitter

        self.generate_literal_tables()

        for module_name, module in self.modules:
            if multi_file:
                emitter = Emitter(self.context)
                emitter.emit_line(
                    f'#include "__native{self.short_group_suffix}.h"')
                emitter.emit_line(
                    f'#include "__native_internal{self.short_group_suffix}.h"')

            self.declare_module(module_name, emitter)
            self.declare_internal_globals(module_name, emitter)
            self.declare_imports(module.imports, emitter)

            for cl in module.classes:
                if cl.is_ext_class:
                    generate_class(cl, module_name, emitter)

            # Generate Python extension module definitions and module initialization functions.
            self.generate_module_def(emitter, module_name, module)

            for fn in module.functions:
                emitter.emit_line()
                generate_native_function(fn, emitter,
                                         self.source_paths[module_name],
                                         module_name)
                if fn.name != TOP_LEVEL_NAME:
                    emitter.emit_line()
                    if is_fastcall_supported(fn, emitter.capi_version):
                        generate_wrapper_function(
                            fn, emitter, self.source_paths[module_name],
                            module_name)
                    else:
                        generate_legacy_wrapper_function(
                            fn, emitter, self.source_paths[module_name],
                            module_name)
            if multi_file:
                name = (
                    f'__native_{emitter.names.private_name(module_name)}.c')
                file_contents.append((name, ''.join(emitter.fragments)))

        # The external header file contains type declarations while
        # the internal contains declarations of functions and objects
        # (which are shared between shared libraries via dynamic
        # exports tables and not accessed directly.)
        ext_declarations = Emitter(self.context)
        ext_declarations.emit_line(
            f'#ifndef MYPYC_NATIVE{self.group_suffix}_H')
        ext_declarations.emit_line(
            f'#define MYPYC_NATIVE{self.group_suffix}_H')
        ext_declarations.emit_line('#include <Python.h>')
        ext_declarations.emit_line('#include <CPy.h>')

        declarations = Emitter(self.context)
        declarations.emit_line(
            f'#ifndef MYPYC_NATIVE_INTERNAL{self.group_suffix}_H')
        declarations.emit_line(
            f'#define MYPYC_NATIVE_INTERNAL{self.group_suffix}_H')
        declarations.emit_line('#include <Python.h>')
        declarations.emit_line('#include <CPy.h>')
        declarations.emit_line(
            f'#include "__native{self.short_group_suffix}.h"')
        declarations.emit_line()
        declarations.emit_line('int CPyGlobalsInit(void);')
        declarations.emit_line()

        for module_name, module in self.modules:
            self.declare_finals(module_name, module.final_names, declarations)
            for cl in module.classes:
                generate_class_type_decl(cl, emitter, ext_declarations,
                                         declarations)
            for fn in module.functions:
                generate_function_declaration(fn, declarations)

        for lib in sorted(self.context.group_deps):
            elib = exported_name(lib)
            short_lib = exported_name(lib.split('.')[-1])
            declarations.emit_lines(
                '#include <{}>'.format(
                    os.path.join(group_dir(lib), f"__native_{short_lib}.h")),
                f'struct export_table_{elib} exports_{elib};')

        sorted_decls = self.toposort_declarations()

        emitter = base_emitter
        self.generate_globals_init(emitter)

        emitter.emit_line()

        for declaration in sorted_decls:
            decls = ext_declarations if declaration.is_type else declarations
            if not declaration.is_type:
                decls.emit_lines(f'extern {declaration.decl[0]}',
                                 *declaration.decl[1:])
                # If there is a definition, emit it. Otherwise repeat the declaration
                # (without an extern).
                if declaration.defn:
                    emitter.emit_lines(*declaration.defn)
                else:
                    emitter.emit_lines(*declaration.decl)
            else:
                decls.emit_lines(*declaration.decl)

        if self.group_name:
            self.generate_export_table(ext_declarations, emitter)

            self.generate_shared_lib_init(emitter)

        ext_declarations.emit_line('#endif')
        declarations.emit_line('#endif')

        output_dir = group_dir(self.group_name) if self.group_name else ''
        return file_contents + [
            (os.path.join(output_dir, f'__native{self.short_group_suffix}.c'),
             ''.join(emitter.fragments)),
            (os.path.join(output_dir,
                          f'__native_internal{self.short_group_suffix}.h'),
             ''.join(declarations.fragments)),
            (os.path.join(output_dir, f'__native{self.short_group_suffix}.h'),
             ''.join(ext_declarations.fragments)),
        ]