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.
(
fn: FuncIR, emitter: Emitter, source_path: str, module_name: str
)
| 132 | |
| 133 | |
| 134 | def generate_wrapper_function( |
| 135 | fn: FuncIR, emitter: Emitter, source_path: str, module_name: str |
| 136 | ) -> None: |
| 137 | """Generate a CPython-compatible vectorcall wrapper for a native function. |
| 138 | |
| 139 | In particular, this handles unboxing the arguments, calling the native function, and |
| 140 | then boxing the return value. |
| 141 | """ |
| 142 | emitter.emit_line(f"{wrapper_function_header(fn, emitter.names)} {{") |
| 143 | |
| 144 | # If fn is a method, then the first argument is a self param |
| 145 | real_args = list(fn.args) |
| 146 | if fn.sig.num_bitmap_args: |
| 147 | real_args = real_args[: -fn.sig.num_bitmap_args] |
| 148 | if fn.class_name and fn.decl.kind != FUNC_STATICMETHOD: |
| 149 | arg = real_args.pop(0) |
| 150 | emitter.emit_line(f"PyObject *obj_{arg.name} = self;") |
| 151 | |
| 152 | # Need to order args as: required, optional, kwonly optional, kwonly required |
| 153 | # This is because CPyArg_ParseStackAndKeywords format string requires |
| 154 | # them grouped in that way. |
| 155 | groups = make_arg_groups(real_args) |
| 156 | reordered_args = reorder_arg_groups(groups) |
| 157 | |
| 158 | emitter.emit_line(make_static_kwlist(reordered_args)) |
| 159 | fmt = make_format_string(fn.name, groups) |
| 160 | # Define the arguments the function accepts (but no types yet) |
| 161 | emitter.emit_line(f'static CPyArg_Parser parser = {{"{fmt}", kwlist, 0}};') |
| 162 | |
| 163 | for arg in real_args: |
| 164 | emitter.emit_line( |
| 165 | "PyObject *obj_{}{};".format(arg.name, " = NULL" if arg.optional else "") |
| 166 | ) |
| 167 | |
| 168 | cleanups = [f"CPy_DECREF(obj_{arg.name});" for arg in groups[ARG_STAR] + groups[ARG_STAR2]] |
| 169 | |
| 170 | arg_ptrs: list[str] = [] |
| 171 | if groups[ARG_STAR] or groups[ARG_STAR2]: |
| 172 | arg_ptrs += [f"&obj_{groups[ARG_STAR][0].name}" if groups[ARG_STAR] else "NULL"] |
| 173 | arg_ptrs += [f"&obj_{groups[ARG_STAR2][0].name}" if groups[ARG_STAR2] else "NULL"] |
| 174 | arg_ptrs += [f"&obj_{arg.name}" for arg in reordered_args] |
| 175 | |
| 176 | if fn.name == "__call__": |
| 177 | nargs = "PyVectorcall_NARGS(nargs)" |
| 178 | else: |
| 179 | nargs = "nargs" |
| 180 | parse_fn = "CPyArg_ParseStackAndKeywords" |
| 181 | # Special case some common signatures |
| 182 | if not real_args: |
| 183 | # No args |
| 184 | parse_fn = "CPyArg_ParseStackAndKeywordsNoArgs" |
| 185 | elif len(real_args) == 1 and len(groups[ARG_POS]) == 1: |
| 186 | # Single positional arg |
| 187 | parse_fn = "CPyArg_ParseStackAndKeywordsOneArg" |
| 188 | elif len(real_args) == len(groups[ARG_POS]) + len(groups[ARG_OPT]): |
| 189 | # No keyword-only args, *args or **kwargs |
| 190 | parse_fn = "CPyArg_ParseStackAndKeywordsSimple" |
| 191 | emitter.emit_lines( |
no test coverage detected
searching dependent graphs…