| """Utilities for emitting C code.""" |
| |
| from mypy.ordered_dict import OrderedDict |
| from typing import List, Set, Dict, Optional, Callable, Union |
| |
| from mypyc.common import ( |
| REG_PREFIX, ATTR_PREFIX, STATIC_PREFIX, TYPE_PREFIX, NATIVE_PREFIX, |
| FAST_ISINSTANCE_MAX_SUBCLASSES, |
| ) |
| from mypyc.ir.ops import BasicBlock, Value |
| from mypyc.ir.rtypes import ( |
| RType, RTuple, RInstance, RUnion, RPrimitive, |
| is_float_rprimitive, is_bool_rprimitive, is_int_rprimitive, is_short_int_rprimitive, |
| is_list_rprimitive, is_dict_rprimitive, is_set_rprimitive, is_tuple_rprimitive, |
| is_none_rprimitive, is_object_rprimitive, object_rprimitive, is_str_rprimitive, |
| int_rprimitive, is_optional_type, optional_value_type, is_int32_rprimitive, |
| is_int64_rprimitive, is_bit_rprimitive |
| ) |
| from mypyc.ir.func_ir import FuncDecl |
| from mypyc.ir.class_ir import ClassIR, all_concrete_classes |
| from mypyc.namegen import NameGenerator, exported_name |
| from mypyc.sametype import is_same_type |
| |
| |
| class HeaderDeclaration: |
| """A representation of a declaration in C. |
| |
| This is used to generate declarations in header files and |
| (optionally) definitions in source files. |
| |
| Attributes: |
| decl: C source code for the declaration. |
| defn: Optionally, C source code for a definition. |
| dependencies: The names of any objects that must be declared prior. |
| is_type: Whether the declaration is of a C type. (C types will be declared in |
| external header files and not marked 'extern'.) |
| needs_export: Whether the declared object needs to be exported to |
| other modules in the linking table. |
| """ |
| |
| def __init__(self, |
| decl: Union[str, List[str]], |
| defn: Optional[List[str]] = None, |
| *, |
| dependencies: Optional[Set[str]] = None, |
| is_type: bool = False, |
| needs_export: bool = False |
| ) -> None: |
| self.decl = [decl] if isinstance(decl, str) else decl |
| self.defn = defn |
| self.dependencies = dependencies or set() |
| self.is_type = is_type |
| self.needs_export = needs_export |
| |
| |
| class EmitterContext: |
| """Shared emitter state for a compilation group.""" |
| |
| def __init__(self, |
| names: NameGenerator, |
| group_name: Optional[str] = None, |
| group_map: Optional[Dict[str, Optional[str]]] = None, |
| ) -> None: |
| """Setup shared emitter state. |
| |
| Args: |
| names: The name generator to use |
| group_map: Map from module names to group name |
| group_name: Current group name |
| """ |
| self.temp_counter = 0 |
| self.names = names |
| self.group_name = group_name |
| self.group_map = group_map or {} |
| # Groups that this group depends on |
| self.group_deps = set() # type: Set[str] |
| |
| # The map below is used for generating declarations and |
| # definitions at the top of the C file. The main idea is that they can |
| # be generated at any time during the emit phase. |
| |
| # A map of a C identifier to whatever the C identifier declares. Currently this is |
| # used for declaring structs and the key corresponds to the name of the struct. |
| # The declaration contains the body of the struct. |
| self.declarations = OrderedDict() # type: Dict[str, HeaderDeclaration] |
| |
| |
| class Emitter: |
| """Helper for C code generation.""" |
| |
| def __init__(self, |
| context: EmitterContext, |
| value_names: Optional[Dict[Value, str]] = None) -> None: |
| self.context = context |
| self.names = context.names |
| self.value_names = value_names or {} |
| self.fragments = [] # type: List[str] |
| self._indent = 0 |
| |
| # Low-level operations |
| |
| def indent(self) -> None: |
| self._indent += 4 |
| |
| def dedent(self) -> None: |
| self._indent -= 4 |
| assert self._indent >= 0 |
| |
| def label(self, label: BasicBlock) -> str: |
| return 'CPyL%s' % label.label |
| |
| def reg(self, reg: Value) -> str: |
| return REG_PREFIX + self.value_names[reg] |
| |
| def attr(self, name: str) -> str: |
| return ATTR_PREFIX + name |
| |
| def emit_line(self, line: str = '') -> None: |
| if line.startswith('}'): |
| self.dedent() |
| self.fragments.append(self._indent * ' ' + line + '\n') |
| if line.endswith('{'): |
| self.indent() |
| |
| def emit_lines(self, *lines: str) -> None: |
| for line in lines: |
| self.emit_line(line) |
| |
| def emit_label(self, label: Union[BasicBlock, str]) -> None: |
| if isinstance(label, str): |
| text = label |
| else: |
| text = self.label(label) |
| # Extra semicolon prevents an error when the next line declares a tempvar |
| self.fragments.append('{}: ;\n'.format(text)) |
| |
| def emit_from_emitter(self, emitter: 'Emitter') -> None: |
| self.fragments.extend(emitter.fragments) |
| |
| def emit_printf(self, fmt: str, *args: str) -> None: |
| fmt = fmt.replace('\n', '\\n') |
| self.emit_line('printf(%s);' % ', '.join(['"%s"' % fmt] + list(args))) |
| self.emit_line('fflush(stdout);') |
| |
| def temp_name(self) -> str: |
| self.context.temp_counter += 1 |
| return '__tmp%d' % self.context.temp_counter |
| |
| def new_label(self) -> str: |
| self.context.temp_counter += 1 |
| return '__LL%d' % self.context.temp_counter |
| |
| def get_module_group_prefix(self, module_name: str) -> str: |
| """Get the group prefix for a module (relative to the current group). |
| |
| The prefix should be prepended to the object name whenever |
| accessing an object from this module. |
| |
| If the module lives is in the current compilation group, there is |
| no prefix. But if it lives in a different group (and hence a separate |
| extension module), we need to access objects from it indirectly via an |
| export table. |
| |
| For example, for code in group `a` to call a function `bar` in group `b`, |
| it would need to do `exports_b.CPyDef_bar(...)`, while code that is |
| also in group `b` can simply do `CPyDef_bar(...)`. |
| |
| Thus the prefix for a module in group `b` is 'exports_b.' if the current |
| group is *not* b and just '' if it is. |
| """ |
| groups = self.context.group_map |
| target_group_name = groups.get(module_name) |
| if target_group_name and target_group_name != self.context.group_name: |
| self.context.group_deps.add(target_group_name) |
| return 'exports_{}.'.format(exported_name(target_group_name)) |
| else: |
| return '' |
| |
| def get_group_prefix(self, obj: Union[ClassIR, FuncDecl]) -> str: |
| """Get the group prefix for an object.""" |
| # See docs above |
| return self.get_module_group_prefix(obj.module_name) |
| |
| def static_name(self, id: str, module: Optional[str], prefix: str = STATIC_PREFIX) -> str: |
| """Create name of a C static variable. |
| |
| These are used for literals and imported modules, among other |
| things. |
| |
| The caller should ensure that the (id, module) pair cannot |
| overlap with other calls to this method within a compilation |
| group. |
| """ |
| lib_prefix = '' if not module else self.get_module_group_prefix(module) |
| # If we are accessing static via the export table, we need to dereference |
| # the pointer also. |
| star_maybe = '*' if lib_prefix else '' |
| suffix = self.names.private_name(module or '', id) |
| return '{}{}{}{}'.format(star_maybe, lib_prefix, prefix, suffix) |
| |
| def type_struct_name(self, cl: ClassIR) -> str: |
| return self.static_name(cl.name, cl.module_name, prefix=TYPE_PREFIX) |
| |
| def ctype(self, rtype: RType) -> str: |
| return rtype._ctype |
| |
| def ctype_spaced(self, rtype: RType) -> str: |
| """Adds a space after ctype for non-pointers.""" |
| ctype = self.ctype(rtype) |
| if ctype[-1] == '*': |
| return ctype |
| else: |
| return ctype + ' ' |
| |
| def c_undefined_value(self, rtype: RType) -> str: |
| if not rtype.is_unboxed: |
| return 'NULL' |
| elif isinstance(rtype, RPrimitive): |
| return rtype.c_undefined |
| elif isinstance(rtype, RTuple): |
| return self.tuple_undefined_value(rtype) |
| assert False, rtype |
| |
| def c_error_value(self, rtype: RType) -> str: |
| return self.c_undefined_value(rtype) |
| |
| def native_function_name(self, fn: FuncDecl) -> str: |
| return '{}{}'.format(NATIVE_PREFIX, fn.cname(self.names)) |
| |
| def tuple_c_declaration(self, rtuple: RTuple) -> List[str]: |
| result = [ |
| '#ifndef MYPYC_DECLARED_{}'.format(rtuple.struct_name), |
| '#define MYPYC_DECLARED_{}'.format(rtuple.struct_name), |
| 'typedef struct {} {{'.format(rtuple.struct_name), |
| ] |
| if len(rtuple.types) == 0: # empty tuple |
| # Empty tuples contain a flag so that they can still indicate |
| # error values. |
| result.append('int empty_struct_error_flag;') |
| else: |
| i = 0 |
| for typ in rtuple.types: |
| result.append('{}f{};'.format(self.ctype_spaced(typ), i)) |
| i += 1 |
| result.append('}} {};'.format(rtuple.struct_name)) |
| values = self.tuple_undefined_value_helper(rtuple) |
| result.append('static {} {} = {{ {} }};'.format( |
| self.ctype(rtuple), self.tuple_undefined_value(rtuple), ''.join(values))) |
| result.append('#endif') |
| result.append('') |
| |
| return result |
| |
| def emit_undefined_attr_check(self, rtype: RType, attr_expr: str, |
| compare: str, |
| unlikely: bool = False) -> None: |
| if isinstance(rtype, RTuple): |
| check = '({})'.format(self.tuple_undefined_check_cond( |
| rtype, attr_expr, self.c_undefined_value, compare) |
| ) |
| else: |
| check = '({} {} {})'.format( |
| attr_expr, compare, self.c_undefined_value(rtype) |
| ) |
| if unlikely: |
| check = '(unlikely{})'.format(check) |
| self.emit_line('if {} {{'.format(check)) |
| |
| def tuple_undefined_check_cond( |
| self, rtuple: RTuple, tuple_expr_in_c: str, |
| c_type_compare_val: Callable[[RType], str], compare: str) -> str: |
| if len(rtuple.types) == 0: |
| # empty tuple |
| return '{}.empty_struct_error_flag {} {}'.format( |
| tuple_expr_in_c, compare, c_type_compare_val(int_rprimitive)) |
| item_type = rtuple.types[0] |
| if isinstance(item_type, RTuple): |
| return self.tuple_undefined_check_cond( |
| item_type, tuple_expr_in_c + '.f0', c_type_compare_val, compare) |
| else: |
| return '{}.f0 {} {}'.format( |
| tuple_expr_in_c, compare, c_type_compare_val(item_type)) |
| |
| def tuple_undefined_value(self, rtuple: RTuple) -> str: |
| return 'tuple_undefined_' + rtuple.unique_id |
| |
| def tuple_undefined_value_helper(self, rtuple: RTuple) -> List[str]: |
| res = [] |
| # see tuple_c_declaration() |
| if len(rtuple.types) == 0: |
| return [self.c_undefined_value(int_rprimitive)] |
| for item in rtuple.types: |
| if not isinstance(item, RTuple): |
| res.append(self.c_undefined_value(item)) |
| else: |
| sub_list = self.tuple_undefined_value_helper(item) |
| res.append('{ ') |
| res.extend(sub_list) |
| res.append(' }') |
| res.append(', ') |
| return res[:-1] |
| |
| # Higher-level operations |
| |
| def declare_tuple_struct(self, tuple_type: RTuple) -> None: |
| if tuple_type.struct_name not in self.context.declarations: |
| dependencies = set() |
| for typ in tuple_type.types: |
| # XXX other types might eventually need similar behavior |
| if isinstance(typ, RTuple): |
| dependencies.add(typ.struct_name) |
| |
| self.context.declarations[tuple_type.struct_name] = HeaderDeclaration( |
| self.tuple_c_declaration(tuple_type), |
| dependencies=dependencies, |
| is_type=True, |
| ) |
| |
| def emit_inc_ref(self, dest: str, rtype: RType) -> None: |
| """Increment reference count of C expression `dest`. |
| |
| For composite unboxed structures (e.g. tuples) recursively |
| increment reference counts for each component. |
| """ |
| if is_int_rprimitive(rtype): |
| self.emit_line('CPyTagged_IncRef(%s);' % dest) |
| elif isinstance(rtype, RTuple): |
| for i, item_type in enumerate(rtype.types): |
| self.emit_inc_ref('{}.f{}'.format(dest, i), item_type) |
| elif not rtype.is_unboxed: |
| self.emit_line('CPy_INCREF(%s);' % dest) |
| # Otherwise assume it's an unboxed, pointerless value and do nothing. |
| |
| def emit_dec_ref(self, dest: str, rtype: RType, is_xdec: bool = False) -> None: |
| """Decrement reference count of C expression `dest`. |
| |
| For composite unboxed structures (e.g. tuples) recursively |
| decrement reference counts for each component. |
| """ |
| x = 'X' if is_xdec else '' |
| if is_int_rprimitive(rtype): |
| self.emit_line('CPyTagged_%sDecRef(%s);' % (x, dest)) |
| elif isinstance(rtype, RTuple): |
| for i, item_type in enumerate(rtype.types): |
| self.emit_dec_ref('{}.f{}'.format(dest, i), item_type, is_xdec) |
| elif not rtype.is_unboxed: |
| self.emit_line('CPy_%sDecRef(%s);' % (x, dest)) |
| # Otherwise assume it's an unboxed, pointerless value and do nothing. |
| |
| def pretty_name(self, typ: RType) -> str: |
| value_type = optional_value_type(typ) |
| if value_type is not None: |
| return '%s or None' % self.pretty_name(value_type) |
| return str(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 emit_union_cast(self, src: str, dest: str, typ: RUnion, declare_dest: bool, |
| err: str, optional: bool, src_type: Optional[RType]) -> None: |
| """Emit cast to a union type. |
| |
| The arguments are similar to emit_cast. |
| """ |
| if declare_dest: |
| self.emit_line('PyObject *{};'.format(dest)) |
| good_label = self.new_label() |
| if optional: |
| self.emit_line('if ({} == NULL) {{'.format(src)) |
| self.emit_line('{} = {};'.format(dest, self.c_error_value(typ))) |
| self.emit_line('goto {};'.format(good_label)) |
| self.emit_line('}') |
| for item in typ.items: |
| self.emit_cast(src, |
| dest, |
| item, |
| declare_dest=False, |
| custom_message='', |
| optional=False, |
| likely=False) |
| self.emit_line('if ({} != NULL) goto {};'.format(dest, good_label)) |
| # Handle cast failure. |
| self.emit_line(err) |
| self.emit_label(good_label) |
| |
| def emit_tuple_cast(self, src: str, dest: str, typ: RTuple, declare_dest: bool, |
| err: str, src_type: Optional[RType]) -> None: |
| """Emit cast to a tuple type. |
| |
| The arguments are similar to emit_cast. |
| """ |
| if declare_dest: |
| self.emit_line('PyObject *{};'.format(dest)) |
| # This reuse of the variable is super dodgy. We don't even |
| # care about the values except to check whether they are |
| # invalid. |
| out_label = self.new_label() |
| self.emit_lines( |
| 'if (unlikely(!(PyTuple_Check({r}) && PyTuple_GET_SIZE({r}) == {size}))) {{'.format( |
| r=src, size=len(typ.types)), |
| '{} = NULL;'.format(dest), |
| 'goto {};'.format(out_label), |
| '}') |
| for i, item in enumerate(typ.types): |
| # Since we did the checks above this should never fail |
| self.emit_cast('PyTuple_GET_ITEM({}, {})'.format(src, i), |
| dest, |
| item, |
| declare_dest=False, |
| custom_message='', |
| optional=False) |
| self.emit_line('if ({} == NULL) goto {};'.format(dest, out_label)) |
| |
| self.emit_line('{} = {};'.format(dest, src)) |
| self.emit_label(out_label) |
| |
| def emit_arg_check(self, src: str, dest: str, typ: RType, check: str, optional: bool) -> None: |
| if optional: |
| self.emit_line('if ({} == NULL) {{'.format(src)) |
| self.emit_line('{} = {};'.format(dest, self.c_error_value(typ))) |
| if check != '': |
| self.emit_line('{}if {}'.format('} else ' if optional else '', check)) |
| elif optional: |
| self.emit_line('else {') |
| |
| 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_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_error_check(self, value: str, rtype: RType, failure: str) -> None: |
| """Emit code for checking a native function return value for uncaught exception.""" |
| if not isinstance(rtype, RTuple): |
| self.emit_line('if ({} == {}) {{'.format(value, self.c_error_value(rtype))) |
| else: |
| if len(rtype.types) == 0: |
| return # empty tuples can't fail. |
| else: |
| cond = self.tuple_undefined_check_cond(rtype, value, self.c_error_value, '==') |
| self.emit_line('if ({}) {{'.format(cond)) |
| self.emit_lines(failure, '}') |
| |
| def emit_gc_visit(self, target: str, rtype: RType) -> None: |
| """Emit code for GC visiting a C variable reference. |
| |
| Assume that 'target' represents a C expression that refers to a |
| struct member, such as 'self->x'. |
| """ |
| if not rtype.is_refcounted: |
| # Not refcounted -> no pointers -> no GC interaction. |
| return |
| elif isinstance(rtype, RPrimitive) and rtype.name == 'builtins.int': |
| self.emit_line('if (CPyTagged_CheckLong({})) {{'.format(target)) |
| self.emit_line('Py_VISIT(CPyTagged_LongAsObject({}));'.format(target)) |
| self.emit_line('}') |
| elif isinstance(rtype, RTuple): |
| for i, item_type in enumerate(rtype.types): |
| self.emit_gc_visit('{}.f{}'.format(target, i), item_type) |
| elif self.ctype(rtype) == 'PyObject *': |
| # The simplest case. |
| self.emit_line('Py_VISIT({});'.format(target)) |
| else: |
| assert False, 'emit_gc_visit() not implemented for %s' % repr(rtype) |
| |
| def emit_gc_clear(self, target: str, rtype: RType) -> None: |
| """Emit code for clearing a C attribute reference for GC. |
| |
| Assume that 'target' represents a C expression that refers to a |
| struct member, such as 'self->x'. |
| """ |
| if not rtype.is_refcounted: |
| # Not refcounted -> no pointers -> no GC interaction. |
| return |
| elif isinstance(rtype, RPrimitive) and rtype.name == 'builtins.int': |
| self.emit_line('if (CPyTagged_CheckLong({})) {{'.format(target)) |
| self.emit_line('CPyTagged __tmp = {};'.format(target)) |
| self.emit_line('{} = {};'.format(target, self.c_undefined_value(rtype))) |
| self.emit_line('Py_XDECREF(CPyTagged_LongAsObject(__tmp));') |
| self.emit_line('}') |
| elif isinstance(rtype, RTuple): |
| for i, item_type in enumerate(rtype.types): |
| self.emit_gc_clear('{}.f{}'.format(target, i), item_type) |
| elif self.ctype(rtype) == 'PyObject *' and self.c_undefined_value(rtype) == 'NULL': |
| # The simplest case. |
| self.emit_line('Py_CLEAR({});'.format(target)) |
| else: |
| assert False, 'emit_gc_clear() not implemented for %s' % repr(rtype) |