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) or is_bit_rprimitive(self.src.type)): self.is_borrowed = True
def visit_rprimitive(self, left: RPrimitive) -> bool: right = self.right if is_bool_rprimitive(left): if is_int_rprimitive(right): return True elif is_bit_rprimitive(left): if is_bool_rprimitive(right) or is_int_rprimitive(right): return True elif is_short_int_rprimitive(left): if is_int_rprimitive(right): return True return left is right
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) or is_bit_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 is_int32_rprimitive(typ): self.emit_line('{}{} = PyLong_FromLong({});'.format(declaration, dest, src)) elif is_int64_rprimitive(typ): self.emit_line('{}{} = PyLong_FromLongLong({});'.format(declaration, dest, src)) 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 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) or is_bit_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) or is_bit_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_short_int_rprimitive(left) and is_int_rprimitive(self.right): return True if is_bit_rprimitive(left) and is_bool_rprimitive(self.right): return True return left is self.right
def emit_unbox(self, src: str, dest: str, typ: RType, *, declare_dest: bool = False, error: Optional[ErrorHandler] = None, raise_exception: bool = True, optional: bool = False, borrow: bool = False) -> None: """Emit code for unboxing a value of given type (from PyObject *). By default, assign error value to dest if the value has an incompatible type and raise TypeError. These can be customized using 'error' and 'raise_exception'. Generate a new reference unless 'borrow' is True. 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' error: What happens on error raise_exception: If True, also raise TypeError on failure borrow: If True, create a borrowed reference """ error = error or AssignHandler() # TODO: Verify refcount handling. if isinstance(error, AssignHandler): failure = f'{dest} = {self.c_error_value(typ)};' elif isinstance(error, GotoHandler): failure = 'goto %s;' % error.label else: assert isinstance(error, ReturnHandler) failure = 'return %s;' % error.value if raise_exception: raise_exc = f'CPy_TypeError("{self.pretty_name(typ)}", {src}); ' failure = raise_exc + failure if is_int_rprimitive(typ) or is_short_int_rprimitive(typ): if declare_dest: self.emit_line(f'CPyTagged {dest};') self.emit_arg_check(src, dest, typ, f'(likely(PyLong_Check({src})))', optional) if borrow: self.emit_line(f' {dest} = CPyTagged_BorrowFromObject({src});') else: self.emit_line(f' {dest} = CPyTagged_FromObject({src});') self.emit_line('else {') self.emit_line(failure) self.emit_line('}') elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line(f'char {dest};') self.emit_arg_check(src, dest, typ, f'(unlikely(!PyBool_Check({src}))) {{', optional) self.emit_line(failure) self.emit_line('} else') conversion = f'{src} == Py_True' self.emit_line(f' {dest} = {conversion};') elif is_none_rprimitive(typ): # Whether we are borrowing or not makes no difference. if declare_dest: self.emit_line(f'char {dest};') self.emit_arg_check(src, dest, typ, f'(unlikely({src} != Py_None)) {{', optional) self.emit_line(failure) self.emit_line('} else') self.emit_line(f' {dest} = 1;') elif isinstance(typ, RTuple): self.declare_tuple_struct(typ) if declare_dest: self.emit_line(f'{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(f'if ({src} == NULL) {{') self.emit_line(f'{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(f'if (unlikely({cast_temp} == NULL)) {{') # self.emit_arg_check(src, dest, typ, # '(!PyTuple_Check({}) || PyTuple_Size({}) != {}) {{'.format( # src, src, len(typ.types)), optional) self.emit_line(failure) # TODO: Decrease refcount? self.emit_line('} else {') if not typ.types: self.emit_line(f'{dest}.empty_struct_error_flag = 0;') 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(f'PyObject *{temp} = PyTuple_GET_ITEM({src}, {i});') temp2 = self.temp_name() # Unbox or check the item. if item_type.is_unboxed: self.emit_unbox(temp, temp2, item_type, raise_exception=raise_exception, error=error, 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'{dest}.f{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, error: Optional[ErrorHandler] = None, raise_exception: bool = True, 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. By default, assign NULL (error value) to dest if the value has an incompatible type and raise TypeError. These can be customized using 'error' and 'raise_exception'. 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' error: What happens on error raise_exception: If True, also raise TypeError on failure likely: If the cast is likely to succeed (can be False for unions) """ error = error or AssignHandler() if isinstance(error, AssignHandler): handle_error = '%s = NULL;' % dest elif isinstance(error, GotoHandler): handle_error = 'goto %s;' % error.label else: assert isinstance(error, ReturnHandler) handle_error = 'return %s;' % error.value if raise_exception: raise_exc = f'CPy_TypeError("{self.pretty_name(typ)}", {src}); ' err = raise_exc + handle_error else: err = handle_error # 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(f'PyObject *{dest};') check = '({} != Py_None)' if likely: check = f'(likely{check})' self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') return # TODO: Verify refcount handling. if (is_list_rprimitive(typ) or is_dict_rprimitive(typ) or is_set_rprimitive(typ) or is_str_rprimitive(typ) or is_range_rprimitive(typ) or is_float_rprimitive(typ) or is_int_rprimitive(typ) or is_bool_rprimitive(typ) or is_bit_rprimitive(typ)): if declare_dest: self.emit_line(f'PyObject *{dest};') if is_list_rprimitive(typ): prefix = 'PyList' elif is_dict_rprimitive(typ): prefix = 'PyDict' elif is_set_rprimitive(typ): prefix = 'PySet' elif is_str_rprimitive(typ): prefix = 'PyUnicode' elif is_range_rprimitive(typ): prefix = 'PyRange' elif is_float_rprimitive(typ): prefix = 'CPyFloat' elif is_int_rprimitive(typ): prefix = 'PyLong' elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ): prefix = 'PyBool' else: assert False, 'unexpected primitive type' check = '({}_Check({}))' if likely: check = f'(likely{check})' self.emit_arg_check(src, dest, typ, check.format(prefix, src), optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') elif is_bytes_rprimitive(typ): if declare_dest: self.emit_line(f'PyObject *{dest};') check = '(PyBytes_Check({}) || PyByteArray_Check({}))' if likely: check = f'(likely{check})' self.emit_arg_check(src, dest, typ, check.format(src, src), optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') elif is_tuple_rprimitive(typ): if declare_dest: self.emit_line(f'{self.ctype(typ)} {dest};') check = '(PyTuple_Check({}))' if likely: check = f'(likely{check})' self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') elif isinstance(typ, RInstance): if declare_dest: self.emit_line(f'PyObject *{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 = f'(likely{check})' self.emit_arg_check(src, dest, typ, check, optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') elif is_none_rprimitive(typ): if declare_dest: self.emit_line(f'PyObject *{dest};') check = '({} == Py_None)' if likely: check = f'(likely{check})' self.emit_arg_check(src, dest, typ, check.format(src), optional) self.emit_lines( f' {dest} = {src};', 'else {', err, '}') elif is_object_rprimitive(typ): if declare_dest: self.emit_line(f'PyObject *{dest};') self.emit_arg_check(src, dest, typ, '', optional) self.emit_line(f'{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