def emit_box(self, src: str, dest: str, typ: RType, declare_dest: bool = False, can_borrow: bool = False) -> None: """Emit code for boxing a value of given type. Generate a simple assignment if no boxing is needed. The source reference count is stolen for the result (no need to decref afterwards). """ # TODO: Always generate a new reference (if a reference type) if declare_dest: declaration = 'PyObject *' else: declaration = '' if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): # Steal the existing reference if it exists. self.emit_line('{}{} = CPyTagged_StealAsObject({});'.format( declaration, dest, src)) elif is_bool_rprimitive(typ): # N.B: bool is special cased to produce a borrowed value # after boxing, so we don't need to increment the refcount # when this comes directly from a Box op. self.emit_lines('{}{} = {} ? Py_True : Py_False;'.format( declaration, dest, src)) if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) elif is_none_rprimitive(typ): # N.B: None is special cased to produce a borrowed value # after boxing, so we don't need to increment the refcount # when this comes directly from a Box op. self.emit_lines('{}{} = Py_None;'.format(declaration, dest)) if not can_borrow: self.emit_inc_ref(dest, object_rprimitive) elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) self.emit_line('{}{} = PyTuple_New({});'.format( declaration, dest, len(typ.types))) self.emit_line('if (unlikely({} == NULL))'.format(dest)) self.emit_line(' CPyError_OutOfMemory();') # TODO: Fail if dest is None for i in range(0, len(typ.types)): if not typ.is_unboxed: self.emit_line('PyTuple_SET_ITEM({}, {}, {}.f{}'.format( dest, i, src, i)) else: inner_name = self.temp_name() self.emit_box('{}.f{}'.format(src, i), inner_name, typ.types[i], declare_dest=True) self.emit_line('PyTuple_SET_ITEM({}, {}, {});'.format( dest, i, inner_name)) else: assert not typ.is_unboxed # Type is boxed -- trivially just assign. self.emit_line('{}{} = {};'.format(declaration, dest, src))
def __init__(self, src: Value, line: int = -1) -> None: super().__init__(line) self.src = src self.type = object_rprimitive # When we box None and bool values, we produce a borrowed result if is_none_rprimitive(self.src.type) or is_bool_rprimitive( self.src.type): self.is_borrowed = True
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 emit_unbox(self, src: str, dest: str, typ: RType, custom_failure: Optional[str] = None, declare_dest: bool = False, borrow: bool = False, optional: bool = False) -> None: """Emit code for unboxing a value of given type (from PyObject *). Evaluate C code in 'failure' if the value has an incompatible type. Always generate a new reference. Args: src: Name of source C variable dest: Name of target C variable typ: Type of value failure: What happens on error declare_dest: If True, also declare the variable 'dest' borrow: If True, create a borrowed reference """ # TODO: Verify refcount handling. raise_exc = 'CPy_TypeError("{}", {});'.format(self.pretty_name(typ), src) if custom_failure is not None: failure = [raise_exc, custom_failure] else: failure = [raise_exc, '%s = %s;' % (dest, self.c_error_value(typ))] if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): if declare_dest: self.emit_line('CPyTagged {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(likely(PyLong_Check({})))'.format(src), optional) if borrow: self.emit_line( ' {} = CPyTagged_BorrowFromObject({});'.format( dest, src)) else: self.emit_line(' {} = CPyTagged_FromObject({});'.format( dest, src)) self.emit_line('else {') self.emit_lines(*failure) self.emit_line('}') elif is_bool_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line('char {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(unlikely(!PyBool_Check({}))) {{'.format(src), optional) self.emit_lines(*failure) self.emit_line('} else') conversion = '{} == Py_True'.format(src) self.emit_line(' {} = {};'.format(dest, conversion)) elif is_none_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line('char {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(unlikely({} != Py_None)) {{'.format(src), optional) self.emit_lines(*failure) self.emit_line('} else') self.emit_line(' {} = 1;'.format(dest)) elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) if declare_dest: self.emit_line('{} {};'.format(self.ctype(typ), dest)) # HACK: The error handling for unboxing tuples is busted # and instead of fixing it I am just wrapping it in the # cast code which I think is right. This is not good. if optional: self.emit_line('if ({} == NULL) {{'.format(src)) self.emit_line('{} = {};'.format(dest, self.c_error_value(typ))) self.emit_line('} else {') cast_temp = self.temp_name() self.emit_tuple_cast(src, cast_temp, typ, declare_dest=True, err='', src_type=None) self.emit_line('if (unlikely({} == NULL)) {{'.format(cast_temp)) # self.emit_arg_check(src, dest, typ, # '(!PyTuple_Check({}) || PyTuple_Size({}) != {}) {{'.format( # src, src, len(typ.types)), optional) self.emit_lines(*failure) # TODO: Decrease refcount? self.emit_line('} else {') if not typ.types: self.emit_line('{}.empty_struct_error_flag = 0;'.format(dest)) for i, item_type in enumerate(typ.types): temp = self.temp_name() # emit_tuple_cast above checks the size, so this should not fail self.emit_line( 'PyObject *{} = PyTuple_GET_ITEM({}, {});'.format( temp, src, i)) temp2 = self.temp_name() # Unbox or check the item. if item_type.is_unboxed: self.emit_unbox(temp, temp2, item_type, custom_failure, declare_dest=True, borrow=borrow) else: if not borrow: self.emit_inc_ref(temp, object_rprimitive) self.emit_cast(temp, temp2, item_type, declare_dest=True) self.emit_line('{}.f{} = {};'.format(dest, i, temp2)) self.emit_line('}') if optional: self.emit_line('}') else: assert False, 'Unboxing not implemented: %s' % typ
def emit_cast(self, src: str, dest: str, typ: RType, declare_dest: bool = False, custom_message: Optional[str] = None, optional: bool = False, src_type: Optional[RType] = None, likely: bool = True) -> None: """Emit code for casting a value of given type. Somewhat strangely, this supports unboxed types but only operates on boxed versions. This is necessary to properly handle types such as Optional[int] in compatibility glue. Assign NULL (error value) to dest if the value has an incompatible type. Always copy/steal the reference in src. Args: src: Name of source C variable dest: Name of target C variable typ: Type of value declare_dest: If True, also declare the variable 'dest' likely: If the cast is likely to succeed (can be False for unions) """ if custom_message is not None: err = custom_message else: err = 'CPy_TypeError("{}", {});'.format(self.pretty_name(typ), src) # Special case casting *from* optional if src_type and is_optional_type( src_type) and not is_object_rprimitive(typ): value_type = optional_value_type(src_type) assert value_type is not None if is_same_type(value_type, typ): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) check = '({} != Py_None)' if likely: check = '(likely{})'.format(check) self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines(' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') return # TODO: Verify refcount handling. if (is_list_rprimitive(typ) or is_dict_rprimitive(typ) or is_set_rprimitive(typ) or is_float_rprimitive(typ) or is_str_rprimitive(typ) or is_int_rprimitive(typ) or is_bool_rprimitive(typ)): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) if is_list_rprimitive(typ): prefix = 'PyList' elif is_dict_rprimitive(typ): prefix = 'PyDict' elif is_set_rprimitive(typ): prefix = 'PySet' elif is_float_rprimitive(typ): prefix = 'CPyFloat' elif is_str_rprimitive(typ): prefix = 'PyUnicode' elif is_int_rprimitive(typ): prefix = 'PyLong' elif is_bool_rprimitive(typ): prefix = 'PyBool' else: assert False, 'unexpected primitive type' check = '({}_Check({}))' if likely: check = '(likely{})'.format(check) self.emit_arg_check(src, dest, typ, check.format(prefix, src), optional) self.emit_lines(' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_tuple_rprimitive(typ): if declare_dest: self.emit_line('{} {};'.format(self.ctype(typ), dest)) check = '(PyTuple_Check({}))' if likely: check = '(likely{})'.format(check) self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines(' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif isinstance(typ, RInstance): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) concrete = all_concrete_classes(typ.class_ir) # If there are too many concrete subclasses or we can't find any # (meaning the code ought to be dead or we aren't doing global opts), # fall back to a normal typecheck. # Otherwise check all the subclasses. if not concrete or len( concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1: check = '(PyObject_TypeCheck({}, {}))'.format( src, self.type_struct_name(typ.class_ir)) else: full_str = '(Py_TYPE({src}) == {targets[0]})' for i in range(1, len(concrete)): full_str += ' || (Py_TYPE({src}) == {targets[%d]})' % i if len(concrete) > 1: full_str = '(%s)' % full_str check = full_str.format( src=src, targets=[self.type_struct_name(ir) for ir in concrete]) if likely: check = '(likely{})'.format(check) self.emit_arg_check(src, dest, typ, check, optional) self.emit_lines(' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_none_rprimitive(typ): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) check = '({} == Py_None)' if likely: check = '(likely{})'.format(check) self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines(' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_object_rprimitive(typ): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) self.emit_arg_check(src, dest, typ, '', optional) self.emit_line('{} = {};'.format(dest, src)) if optional: self.emit_line('}') elif isinstance(typ, RUnion): self.emit_union_cast(src, dest, typ, declare_dest, err, optional, src_type) elif isinstance(typ, RTuple): assert not optional self.emit_tuple_cast(src, dest, typ, declare_dest, err, src_type) else: assert False, 'Cast not implemented: %s' % typ
def visit_rprimitive(self, left: RPrimitive) -> bool: if is_bool_rprimitive(left) and is_int_rprimitive(self.right): return True if is_short_int_rprimitive(left) and is_int_rprimitive(self.right): return True return left is self.right
def to_str(self, env: Environment) -> str: s = env.format('%sdec_ref %r', 'x' if self.is_xdec else '', self.src) if is_bool_rprimitive(self.src.type) or is_int_rprimitive( self.src.type): s += ' :: {}'.format(short_name(self.src.type.name)) return s