def generate_wrapper_core(fn: FuncIR, emitter: Emitter, optional_args: List[RuntimeArg] = [], arg_names: Optional[List[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. """ arg_names = arg_names or [arg.name for arg in fn.args] for arg_name, arg in zip(arg_names, fn.args): generate_arg_check(arg_name, arg.type, emitter, arg in optional_args) native_args = ', '.join('arg_{}'.format(arg) for arg in arg_names) if fn.ret_type.is_unboxed: # 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_error_check('retval', fn.ret_type, 'return NULL;') emitter.emit_box('retval', 'retbox', fn.ret_type, declare_dest=True) emitter.emit_line('return retbox;') else: emitter.emit_line('return {}{}({});'.format(NATIVE_PREFIX, fn.cname(emitter.names), native_args))
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;')
def generate_wrapper_function(fn: FuncIR, emitter: Emitter) -> None: """Generates a CPython-compatible wrapper function 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: arg = real_args.pop(0) emitter.emit_line('PyObject *obj_{} = self;'.format(arg.name)) arg_names = ''.join('"{}", '.format(arg.name) for arg in real_args) emitter.emit_line('static char *kwlist[] = {{{}0}};'.format(arg_names)) for arg in real_args: emitter.emit_line('PyObject *obj_{};'.format(arg.name)) arg_spec = 'O' * len(real_args) arg_ptrs = ''.join(', &obj_{}'.format(arg.name) for arg in real_args) emitter.emit_lines( 'if (!PyArg_ParseTupleAndKeywords(args, kw, "{}:f", kwlist{})) {{'.format( arg_spec, arg_ptrs), 'return NULL;', '}') for arg in fn.args: generate_arg_check(arg.name, arg.type, emitter) native_args = ', '.join('arg_{}'.format(arg.name) for arg in fn.args) if fn.ret_type.is_unboxed: # TODO: The Py_RETURN macros return the correct PyObject * with reference count handling. # Are they relevant? ret_type = fn.ret_type emitter.emit_line('{}retval = {}{}({});'.format(emitter.ctype_spaced(ret_type), NATIVE_PREFIX, fn.cname(emitter.names), native_args)) emitter.emit_error_check('retval', ret_type, 'return NULL;') emitter.emit_box('retval', 'retbox', ret_type, declare_dest=True) emitter.emit_lines('return retbox;') else: emitter.emit_line('return {}{}({});'.format(NATIVE_PREFIX, fn.cname(emitter.names), native_args)) # TODO: Tracebacks? emitter.emit_line('}')
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_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