def generate_bin_op_forward_only_wrapper(fn: FuncIR, emitter: Emitter, gen: 'WrapperGenerator') -> None: gen.emit_arg_processing(error=GotoHandler('typefail'), raise_exception=False) gen.emit_call(not_implemented_handler='goto typefail;') gen.emit_error_handling() emitter.emit_label('typefail') # If some argument has an incompatible type, treat this the same as # returning NotImplemented, and try to call the reverse operator method. # # Note that in normal Python you'd instead of an explicit # return of NotImplemented, but it doesn't generally work here # the body won't be executed at all if there is an argument # type check failure. # # The recommended way is to still use a type check in the # body. This will only be used in interpreted mode: # # def __add__(self, other: int) -> Foo: # if not isinstance(other, int): # return NotImplemented # ... rmethod = reverse_op_methods[fn.name] emitter.emit_line('_Py_IDENTIFIER({});'.format(rmethod)) emitter.emit_line( 'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});'.format( op_methods_to_symbols[fn.name], rmethod)) gen.finish()
def error(self) -> ErrorHandler: """Figure out how to deal with errors in the wrapper.""" if self.cleanups or self.traceback_code: # We'll have a label at the end with error handling code. return GotoHandler('fail') else: # Nothing special needs to done to handle errors, so just return. return ReturnHandler('NULL')
def generate_bin_op_reverse_only_wrapper(emitter: Emitter, gen: 'WrapperGenerator') -> None: gen.arg_names = ['right', 'left'] gen.emit_arg_processing(error=GotoHandler('typefail'), raise_exception=False) gen.emit_call() gen.emit_error_handling() emitter.emit_label('typefail') emitter.emit_line('Py_INCREF(Py_NotImplemented);') emitter.emit_line('return Py_NotImplemented;') gen.finish()
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 = ReturnHandler('NULL') if not use_goto else GotoHandler('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, optional=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_bin_op_both_wrappers(cl: ClassIR, fn: FuncIR, fn_rev: FuncIR, emitter: Emitter, gen: 'WrapperGenerator') -> None: # There's both a forward and a reverse operator method. First # check if we should try calling the forward one. If the # argument type check fails, fall back to the reverse method. # # Similar to above, we can't perfectly match Python semantics. # In regular Python code you'd return NotImplemented if the # operand has the wrong type, but in compiled code we'll never # get to execute the type check. emitter.emit_line('if (PyObject_IsInstance(obj_left, (PyObject *){})) {{'.format( emitter.type_struct_name(cl))) gen.emit_arg_processing(error=GotoHandler('typefail'), raise_exception=False) gen.emit_call(not_implemented_handler='goto typefail;') gen.emit_error_handling() emitter.emit_line('}') emitter.emit_label('typefail') emitter.emit_line('if (PyObject_IsInstance(obj_right, (PyObject *){})) {{'.format( emitter.type_struct_name(cl))) gen.set_target(fn_rev) gen.arg_names = ['right', 'left'] gen.emit_arg_processing(error=GotoHandler('typefail2'), raise_exception=False) gen.emit_call() gen.emit_error_handling() emitter.emit_line('} else {') emitter.emit_line('_Py_IDENTIFIER({});'.format(fn_rev.name)) emitter.emit_line( 'return CPy_CallReverseOpMethod(obj_left, obj_right, "{}", &PyId_{});'.format( op_methods_to_symbols[fn.name], fn_rev.name)) emitter.emit_line('}') emitter.emit_label('typefail2') emitter.emit_line('Py_INCREF(Py_NotImplemented);') emitter.emit_line('return Py_NotImplemented;') gen.finish()
def generate_set_del_item_wrapper_inner(fn: FuncIR, emitter: Emitter, args: Sequence[RuntimeArg]) -> None: for arg in args: generate_arg_check(arg.name, arg.type, emitter, GotoHandler('fail')) native_args = ', '.join('arg_{}'.format(arg.name) for arg in args) emitter.emit_line('{}val = {}{}({});'.format(emitter.ctype_spaced(fn.ret_type), NATIVE_PREFIX, fn.cname(emitter.names), native_args)) emitter.emit_error_check('val', fn.ret_type, 'goto fail;') emitter.emit_dec_ref('val', fn.ret_type) emitter.emit_line('return 0;') emitter.emit_label('fail') emitter.emit_line('return -1;') emitter.emit_line('}')
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. """ gen = WrapperGenerator(None, emitter) gen.set_target(fn) gen.arg_names = arg_names or [arg.name for arg in fn.args] gen.cleanups = cleanups or [] gen.optional_args = optional_args or [] gen.traceback_code = traceback_code or '' error = ReturnHandler('NULL') if not gen.use_goto() else GotoHandler('fail') gen.emit_arg_processing(error=error) gen.emit_call() gen.emit_error_handling()