Esempio n. 1
0
def is_fastcall_supported(fn: FuncIR, capi_version: Tuple[int, int]) -> bool:
    if fn.class_name is not None:
        if fn.name == '__call__':
            # We can use vectorcalls (PEP 590) when supported
            return use_vectorcall(capi_version)
        # TODO: Support fastcall for __init__.
        return use_fastcall(capi_version) and fn.name != '__init__'
    return use_fastcall(capi_version)
Esempio n. 2
0
 def use_vectorcall(self) -> bool:
     return use_vectorcall(self.capi_version)
Esempio n. 3
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('}')