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 visit_load_static(self, op: LoadStatic) -> None: dest = self.reg(op) prefix = self.PREFIX_MAP[op.namespace] name = self.emitter.static_name(op.identifier, op.module_name, prefix) if op.namespace == NAMESPACE_TYPE: name = '(PyObject *)&%s' % name if is_int_rprimitive(op.type): self.emit_line('%s = CPyTagged_FromObject(%s);' % (dest, name)) else: self.emit_line('%s = %s;' % (dest, name))
def emit_dec_ref(self, dest: str, rtype: RType) -> None: """Decrement reference count of C expression `dest`. For composite unboxed structures (e.g. tuples) recursively decrement reference counts for each component. """ if is_int_rprimitive(rtype): self.emit_line('CPyTagged_DecRef(%s);' % dest) elif isinstance(rtype, RTuple): for i, item_type in enumerate(rtype.types): self.emit_dec_ref('{}.f{}'.format(dest, i), item_type) elif not rtype.is_unboxed: self.emit_line('CPy_DECREF(%s);' % dest)
def visit_load_static(self, op: LoadStatic) -> None: dest = self.reg(op) prefix = self.PREFIX_MAP[op.namespace] name = self.emitter.static_name(op.identifier, op.module_name, prefix) if op.namespace == NAMESPACE_TYPE: name = '(PyObject *)%s' % name if is_int_rprimitive(op.type): self.emit_line('%s = CPyTagged_FromObject(%s);' % (dest, name)) else: ann = '' if op.ann: s = repr(op.ann) if not any(x in s for x in ('/*', '*/', '\0')): ann = ' /* %s */' % s self.emit_line('%s = %s;%s' % (dest, name, ann))
def emit_box(self, src: str, dest: str, typ: RType, declare_dest: bool = False) -> None: """Emit code for boxing a value of give 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): # Steal the existing reference if it exists. self.emit_line('{}{} = CPyTagged_StealAsObject({});'.format( declaration, dest, src)) elif is_bool_rprimitive(typ): # TODO: The Py_RETURN macros return the correct PyObject * with reference count # handling. Relevant here? self.emit_lines('{}{} = PyBool_FromLong({});'.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 ({} == 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_SetItem({}, {}, {}.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_SetItem({}, {}, {});'.format( dest, i, inner_name, i)) else: assert not typ.is_unboxed # Type is boxed -- trivially just assign. self.emit_line('{}{} = {};'.format(declaration, dest, src))
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
def visit_rprimitive(self, left: RPrimitive) -> bool: if is_short_int_rprimitive(left) and is_int_rprimitive(self.right): return True return left is self.right
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: Raise exception on failure. # TODO: Verify refcount handling. raise_exc = 'PyErr_SetString(PyExc_TypeError, "%s object expected");' % ( self.pretty_name(typ)) 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): if declare_dest: self.emit_line('CPyTagged {};'.format(dest)) self.emit_arg_check(src, dest, typ, '(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, '(!PyBool_Check({})) {{'.format(src), optional) self.emit_lines(*failure) self.emit_line('} else') conversion = 'PyObject_IsTrue({})'.format(src) self.emit_line(' {} = {};'.format(dest, conversion)) 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 ({} == 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() self.emit_line( 'PyObject *{} = PyTuple_GetItem({}, {});'.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) -> 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 compatability 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' """ if custom_message is not None: err = custom_message else: err = 'PyErr_SetString(PyExc_TypeError, "{} object expected");'.format( self.pretty_name(typ)) # 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)) self.emit_arg_check(src, dest, typ, '({} != Py_None)'.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, prefix 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)) self.emit_arg_check(src, dest, typ, '(PyTuple_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)) if typ.class_ir.children: check = '(PyObject_TypeCheck({}, {}))'.format( src, self.type_struct_name(typ.class_ir)) else: # If the class has no children, just check the type directly check = '(Py_TYPE({}) == {})'.format( src, self.type_struct_name(typ.class_ir)) 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)) self.emit_arg_check(src, dest, typ, '({} == Py_None)'.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 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) n_types = len(concrete) # If there are too many concrete subclasses or we can't find any # (meaning the code ought to be dead), fall back to a normal typecheck. # Otherwise check all the subclasses. if n_types == 0 or n_types > 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, n_types): full_str += ' || (Py_TYPE({src}) == {targets[%d]})' % i if n_types > 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 emit_cast(self, src: str, dest: str, typ: RType, declare_dest: bool = False, custom_message: Optional[str] = None) -> 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 compatability 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' """ if custom_message is not None: err = custom_message else: err = 'PyErr_SetString(PyExc_TypeError, "{} object expected");'.format( self.pretty_name(typ)) # 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 = 'PyFloat' elif is_str_rprimitive(typ): prefix = 'PyUnicode' elif is_int_rprimitive(typ): prefix = 'PyLong' elif is_bool_rprimitive(typ): prefix = 'PyBool' else: assert False, prefix self.emit_lines('if ({}_Check({}))'.format(prefix, src), ' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_tuple_rprimitive(typ): if declare_dest: self.emit_line('{} {};'.format(self.ctype(typ), dest)) self.emit_lines('if (PyTuple_Check({}))'.format(src), ' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif isinstance(typ, RInstance): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) self.emit_lines( 'if (PyObject_TypeCheck({}, &{}))'.format( src, self.type_struct_name(typ.class_ir)), ' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_none_rprimitive(typ): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) self.emit_lines('if ({} == Py_None)'.format(src), ' {} = {};'.format(dest, src), 'else {', err, '{} = NULL;'.format(dest), '}') elif is_object_rprimitive(typ): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) self.emit_line('{} = {};'.format(dest, src)) elif isinstance(typ, ROptional): if declare_dest: self.emit_line('PyObject *{};'.format(dest)) self.emit_lines('if ({} == Py_None)'.format(src), ' {} = {};'.format(dest, src), 'else {') self.emit_cast(src, dest, typ.value_type, custom_message=err) self.emit_line('}') 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 return isinstance(self.right, RPrimitive) and left.name == self.right.name