| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193 |
- """Utilities for emitting C code."""
- from __future__ import annotations
- import pprint
- import sys
- import textwrap
- from typing import Callable, Final
- from mypyc.codegen.literals import Literals
- from mypyc.common import (
- ATTR_PREFIX,
- BITMAP_BITS,
- FAST_ISINSTANCE_MAX_SUBCLASSES,
- NATIVE_PREFIX,
- REG_PREFIX,
- STATIC_PREFIX,
- TYPE_PREFIX,
- use_vectorcall,
- )
- from mypyc.ir.class_ir import ClassIR, all_concrete_classes
- from mypyc.ir.func_ir import FuncDecl
- from mypyc.ir.ops import BasicBlock, Value
- from mypyc.ir.rtypes import (
- RInstance,
- RPrimitive,
- RTuple,
- RType,
- RUnion,
- int_rprimitive,
- is_bit_rprimitive,
- is_bool_rprimitive,
- is_bytes_rprimitive,
- is_dict_rprimitive,
- is_fixed_width_rtype,
- is_float_rprimitive,
- is_int16_rprimitive,
- is_int32_rprimitive,
- is_int64_rprimitive,
- is_int_rprimitive,
- is_list_rprimitive,
- is_none_rprimitive,
- is_object_rprimitive,
- is_optional_type,
- is_range_rprimitive,
- is_set_rprimitive,
- is_short_int_rprimitive,
- is_str_rprimitive,
- is_tuple_rprimitive,
- is_uint8_rprimitive,
- object_rprimitive,
- optional_value_type,
- )
- from mypyc.namegen import NameGenerator, exported_name
- from mypyc.sametype import is_same_type
- # Whether to insert debug asserts for all error handling, to quickly
- # catch errors propagating without exceptions set.
- DEBUG_ERRORS: Final = False
- 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: str | list[str],
- defn: list[str] | None = None,
- *,
- dependencies: set[str] | None = 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: str | None = None,
- group_map: dict[str, str | None] | None = 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[str] = set()
- # 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: dict[str, HeaderDeclaration] = {}
- self.literals = Literals()
- class ErrorHandler:
- """Describes handling errors in unbox/cast operations."""
- class AssignHandler(ErrorHandler):
- """Assign an error value on error."""
- class GotoHandler(ErrorHandler):
- """Goto label on error."""
- def __init__(self, label: str) -> None:
- self.label = label
- class TracebackAndGotoHandler(ErrorHandler):
- """Add traceback item and goto label on error."""
- def __init__(
- self, label: str, source_path: str, module_name: str, traceback_entry: tuple[str, int]
- ) -> None:
- self.label = label
- self.source_path = source_path
- self.module_name = module_name
- self.traceback_entry = traceback_entry
- class ReturnHandler(ErrorHandler):
- """Return a constant value on error."""
- def __init__(self, value: str) -> None:
- self.value = value
- class Emitter:
- """Helper for C code generation."""
- def __init__(
- self,
- context: EmitterContext,
- value_names: dict[Value, str] | None = None,
- capi_version: tuple[int, int] | None = None,
- ) -> None:
- self.context = context
- self.capi_version = capi_version or sys.version_info[:2]
- self.names = context.names
- self.value_names = value_names or {}
- self.fragments: 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 object_annotation(self, obj: object, line: str) -> str:
- """Build a C comment with an object's string represention.
- If the comment exceeds the line length limit, it's wrapped into a
- multiline string (with the extra lines indented to be aligned with
- the first line's comment).
- If it contains illegal characters, an empty string is returned."""
- line_width = self._indent + len(line)
- formatted = pprint.pformat(obj, compact=True, width=max(90 - line_width, 20))
- if any(x in formatted for x in ("/*", "*/", "\0")):
- return ""
- if "\n" in formatted:
- first_line, rest = formatted.split("\n", maxsplit=1)
- comment_continued = textwrap.indent(rest, (line_width + 3) * " ")
- return f" /* {first_line}\n{comment_continued} */"
- else:
- return f" /* {formatted} */"
- def emit_line(self, line: str = "", *, ann: object = None) -> None:
- if line.startswith("}"):
- self.dedent()
- comment = self.object_annotation(ann, line) if ann is not None else ""
- self.fragments.append(self._indent * " " + line + comment + "\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: BasicBlock | str) -> None:
- if isinstance(label, str):
- text = label
- else:
- if label.label == 0 or not label.referenced:
- return
- text = self.label(label)
- # Extra semicolon prevents an error when the next line declares a tempvar
- self.fragments.append(f"{text}: ;\n")
- 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 f"exports_{exported_name(target_group_name)}."
- else:
- return ""
- def get_group_prefix(self, obj: 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: str | None, 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 f"{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 f"{NATIVE_PREFIX}{fn.cname(self.names)}"
- def tuple_c_declaration(self, rtuple: RTuple) -> list[str]:
- result = [
- f"#ifndef MYPYC_DECLARED_{rtuple.struct_name}",
- f"#define MYPYC_DECLARED_{rtuple.struct_name}",
- f"typedef struct {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"{self.ctype_spaced(typ)}f{i};")
- i += 1
- result.append(f"}} {rtuple.struct_name};")
- result.append("#endif")
- result.append("")
- return result
- def bitmap_field(self, index: int) -> str:
- """Return C field name used for attribute bitmap."""
- n = index // BITMAP_BITS
- if n == 0:
- return "bitmap"
- return f"bitmap{n + 1}"
- def attr_bitmap_expr(self, obj: str, cl: ClassIR, index: int) -> str:
- """Return reference to the attribute definedness bitmap."""
- cast = f"({cl.struct_name(self.names)} *)"
- attr = self.bitmap_field(index)
- return f"({cast}{obj})->{attr}"
- def emit_attr_bitmap_set(
- self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str
- ) -> None:
- """Mark an attribute as defined in the attribute bitmap.
- Assumes that the attribute is tracked in the bitmap (only some attributes
- use the bitmap). If 'value' is not equal to the error value, do nothing.
- """
- self._emit_attr_bitmap_update(value, obj, rtype, cl, attr, clear=False)
- def emit_attr_bitmap_clear(self, obj: str, rtype: RType, cl: ClassIR, attr: str) -> None:
- """Mark an attribute as undefined in the attribute bitmap.
- Unlike emit_attr_bitmap_set, clear unconditionally.
- """
- self._emit_attr_bitmap_update("", obj, rtype, cl, attr, clear=True)
- def _emit_attr_bitmap_update(
- self, value: str, obj: str, rtype: RType, cl: ClassIR, attr: str, clear: bool
- ) -> None:
- if value:
- check = self.error_value_check(rtype, value, "==")
- self.emit_line(f"if (unlikely({check})) {{")
- index = cl.bitmap_attrs.index(attr)
- mask = 1 << (index & (BITMAP_BITS - 1))
- bitmap = self.attr_bitmap_expr(obj, cl, index)
- if clear:
- self.emit_line(f"{bitmap} &= ~{mask};")
- else:
- self.emit_line(f"{bitmap} |= {mask};")
- if value:
- self.emit_line("}")
- def use_vectorcall(self) -> bool:
- return use_vectorcall(self.capi_version)
- def emit_undefined_attr_check(
- self,
- rtype: RType,
- attr_expr: str,
- compare: str,
- obj: str,
- attr: str,
- cl: ClassIR,
- *,
- unlikely: bool = False,
- ) -> None:
- check = self.error_value_check(rtype, attr_expr, compare)
- if unlikely:
- check = f"unlikely({check})"
- if rtype.error_overlap:
- index = cl.bitmap_attrs.index(attr)
- bit = 1 << (index & (BITMAP_BITS - 1))
- attr = self.bitmap_field(index)
- obj_expr = f"({cl.struct_name(self.names)} *){obj}"
- check = f"{check} && !(({obj_expr})->{attr} & {bit})"
- self.emit_line(f"if ({check}) {{")
- def error_value_check(self, rtype: RType, value: str, compare: str) -> str:
- if isinstance(rtype, RTuple):
- return self.tuple_undefined_check_cond(
- rtype, value, self.c_error_value, compare, check_exception=False
- )
- else:
- return f"{value} {compare} {self.c_error_value(rtype)}"
- def tuple_undefined_check_cond(
- self,
- rtuple: RTuple,
- tuple_expr_in_c: str,
- c_type_compare_val: Callable[[RType], str],
- compare: str,
- *,
- check_exception: bool = True,
- ) -> 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)
- )
- if rtuple.error_overlap:
- i = 0
- item_type = rtuple.types[0]
- else:
- for i, typ in enumerate(rtuple.types):
- if not typ.error_overlap:
- item_type = rtuple.types[i]
- break
- else:
- assert False, "not expecting tuple with error overlap"
- if isinstance(item_type, RTuple):
- return self.tuple_undefined_check_cond(
- item_type, tuple_expr_in_c + f".f{i}", c_type_compare_val, compare
- )
- else:
- check = f"{tuple_expr_in_c}.f{i} {compare} {c_type_compare_val(item_type)}"
- if rtuple.error_overlap and check_exception:
- check += " && PyErr_Occurred()"
- return check
- def tuple_undefined_value(self, rtuple: RTuple) -> str:
- """Undefined tuple value suitable in an expression."""
- return f"({rtuple.struct_name}) {self.c_initializer_undefined_value(rtuple)}"
- def c_initializer_undefined_value(self, rtype: RType) -> str:
- """Undefined value represented in a form suitable for variable initialization."""
- if isinstance(rtype, RTuple):
- if not rtype.types:
- # Empty tuples contain a flag so that they can still indicate
- # error values.
- return f"{{ {int_rprimitive.c_undefined} }}"
- items = ", ".join([self.c_initializer_undefined_value(t) for t in rtype.types])
- return f"{{ {items} }}"
- else:
- return self.c_undefined_value(rtype)
- # 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, *, rare: bool = False) -> None:
- """Increment reference count of C expression `dest`.
- For composite unboxed structures (e.g. tuples) recursively
- increment reference counts for each component.
- If rare is True, optimize for code size and compilation speed.
- """
- if is_int_rprimitive(rtype):
- if rare:
- self.emit_line("CPyTagged_IncRef(%s);" % dest)
- else:
- self.emit_line("CPyTagged_INCREF(%s);" % dest)
- elif isinstance(rtype, RTuple):
- for i, item_type in enumerate(rtype.types):
- self.emit_inc_ref(f"{dest}.f{i}", item_type)
- elif not rtype.is_unboxed:
- # Always inline, since this is a simple op
- 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, rare: bool = False
- ) -> None:
- """Decrement reference count of C expression `dest`.
- For composite unboxed structures (e.g. tuples) recursively
- decrement reference counts for each component.
- If rare is True, optimize for code size and compilation speed.
- """
- x = "X" if is_xdec else ""
- if is_int_rprimitive(rtype):
- if rare:
- self.emit_line(f"CPyTagged_{x}DecRef({dest});")
- else:
- # Inlined
- self.emit_line(f"CPyTagged_{x}DECREF({dest});")
- elif isinstance(rtype, RTuple):
- for i, item_type in enumerate(rtype.types):
- self.emit_dec_ref(f"{dest}.f{i}", item_type, is_xdec=is_xdec, rare=rare)
- elif not rtype.is_unboxed:
- if rare:
- self.emit_line(f"CPy_{x}DecRef({dest});")
- else:
- # Inlined
- self.emit_line(f"CPy_{x}DECREF({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,
- error: ErrorHandler | None = None,
- raise_exception: bool = True,
- optional: bool = False,
- src_type: RType | None = 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()
- # 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 {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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)
- or is_fixed_width_rtype(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) or is_fixed_width_rtype(typ):
- # TODO: Range check for fixed-width types?
- prefix = "PyLong"
- elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ):
- prefix = "PyBool"
- else:
- assert False, f"unexpected primitive type: {typ}"
- 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 {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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 {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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 {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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};".format(dest, src), "else {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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 {")
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_line("}")
- 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, error, optional, src_type, raise_exception
- )
- elif isinstance(typ, RTuple):
- assert not optional
- self.emit_tuple_cast(src, dest, typ, declare_dest, error, src_type)
- else:
- assert False, "Cast not implemented: %s" % typ
- def emit_cast_error_handler(
- self, error: ErrorHandler, src: str, dest: str, typ: RType, raise_exception: bool
- ) -> None:
- if raise_exception:
- if isinstance(error, TracebackAndGotoHandler):
- # Merge raising and emitting traceback entry into a single call.
- self.emit_type_error_traceback(
- error.source_path, error.module_name, error.traceback_entry, typ=typ, src=src
- )
- self.emit_line("goto %s;" % error.label)
- return
- self.emit_line(f'CPy_TypeError("{self.pretty_name(typ)}", {src}); ')
- if isinstance(error, AssignHandler):
- self.emit_line("%s = NULL;" % dest)
- elif isinstance(error, GotoHandler):
- self.emit_line("goto %s;" % error.label)
- elif isinstance(error, TracebackAndGotoHandler):
- self.emit_line("%s = NULL;" % dest)
- self.emit_traceback(error.source_path, error.module_name, error.traceback_entry)
- self.emit_line("goto %s;" % error.label)
- else:
- assert isinstance(error, ReturnHandler)
- self.emit_line("return %s;" % error.value)
- def emit_union_cast(
- self,
- src: str,
- dest: str,
- typ: RUnion,
- declare_dest: bool,
- error: ErrorHandler,
- optional: bool,
- src_type: RType | None,
- raise_exception: bool,
- ) -> None:
- """Emit cast to a union type.
- The arguments are similar to emit_cast.
- """
- if declare_dest:
- self.emit_line(f"PyObject *{dest};")
- good_label = self.new_label()
- if optional:
- self.emit_line(f"if ({src} == NULL) {{")
- self.emit_line(f"{dest} = {self.c_error_value(typ)};")
- self.emit_line(f"goto {good_label};")
- self.emit_line("}")
- for item in typ.items:
- self.emit_cast(
- src,
- dest,
- item,
- declare_dest=False,
- raise_exception=False,
- optional=False,
- likely=False,
- )
- self.emit_line(f"if ({dest} != NULL) goto {good_label};")
- # Handle cast failure.
- self.emit_cast_error_handler(error, src, dest, typ, raise_exception)
- self.emit_label(good_label)
- def emit_tuple_cast(
- self,
- src: str,
- dest: str,
- typ: RTuple,
- declare_dest: bool,
- error: ErrorHandler,
- src_type: RType | None,
- ) -> None:
- """Emit cast to a tuple type.
- The arguments are similar to emit_cast.
- """
- if declare_dest:
- self.emit_line(f"PyObject *{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)
- ),
- f"{dest} = NULL;",
- f"goto {out_label};",
- "}",
- )
- for i, item in enumerate(typ.types):
- # Since we did the checks above this should never fail
- self.emit_cast(
- f"PyTuple_GET_ITEM({src}, {i})",
- dest,
- item,
- declare_dest=False,
- raise_exception=False,
- optional=False,
- )
- self.emit_line(f"if ({dest} == NULL) goto {out_label};")
- self.emit_line(f"{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(f"if ({src} == NULL) {{")
- self.emit_line(f"{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,
- *,
- declare_dest: bool = False,
- error: ErrorHandler | None = 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 is_int64_rprimitive(typ):
- # Whether we are borrowing or not makes no difference.
- assert not optional # Not supported for overlapping error values
- if declare_dest:
- self.emit_line(f"int64_t {dest};")
- self.emit_line(f"{dest} = CPyLong_AsInt64({src});")
- if not isinstance(error, AssignHandler):
- self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
- elif is_int32_rprimitive(typ):
- # Whether we are borrowing or not makes no difference.
- assert not optional # Not supported for overlapping error values
- if declare_dest:
- self.emit_line(f"int32_t {dest};")
- self.emit_line(f"{dest} = CPyLong_AsInt32({src});")
- if not isinstance(error, AssignHandler):
- self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
- elif is_int16_rprimitive(typ):
- # Whether we are borrowing or not makes no difference.
- assert not optional # Not supported for overlapping error values
- if declare_dest:
- self.emit_line(f"int16_t {dest};")
- self.emit_line(f"{dest} = CPyLong_AsInt16({src});")
- if not isinstance(error, AssignHandler):
- self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
- elif is_uint8_rprimitive(typ):
- # Whether we are borrowing or not makes no difference.
- assert not optional # Not supported for overlapping error values
- if declare_dest:
- self.emit_line(f"uint8_t {dest};")
- self.emit_line(f"{dest} = CPyLong_AsUInt8({src});")
- if not isinstance(error, AssignHandler):
- self.emit_unbox_failure_with_overlapping_error_value(dest, typ, failure)
- elif is_float_rprimitive(typ):
- assert not optional # Not supported for overlapping error values
- if declare_dest:
- self.emit_line(f"double {dest};")
- # TODO: Don't use __float__ and __index__
- self.emit_line(f"{dest} = PyFloat_AsDouble({src});")
- self.emit_lines(f"if ({dest} == -1.0 && PyErr_Occurred()) {{", failure, "}")
- 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, error=error, 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_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(f"{declaration}{dest} = CPyTagged_StealAsObject({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(f"{declaration}{dest} = {src} ? Py_True : Py_False;")
- 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(f"{declaration}{dest} = Py_None;")
- if not can_borrow:
- self.emit_inc_ref(dest, object_rprimitive)
- elif is_int32_rprimitive(typ) or is_int16_rprimitive(typ) or is_uint8_rprimitive(typ):
- self.emit_line(f"{declaration}{dest} = PyLong_FromLong({src});")
- elif is_int64_rprimitive(typ):
- self.emit_line(f"{declaration}{dest} = PyLong_FromLongLong({src});")
- elif is_float_rprimitive(typ):
- self.emit_line(f"{declaration}{dest} = PyFloat_FromDouble({src});")
- elif isinstance(typ, RTuple):
- self.declare_tuple_struct(typ)
- self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});")
- self.emit_line(f"if (unlikely({dest} == NULL))")
- 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(f"PyTuple_SET_ITEM({dest}, {i}, {src}.f{i}")
- else:
- inner_name = self.temp_name()
- self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
- self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
- else:
- assert not typ.is_unboxed
- # Type is boxed -- trivially just assign.
- self.emit_line(f"{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 isinstance(rtype, RTuple):
- 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(f"if ({cond}) {{")
- elif rtype.error_overlap:
- # The error value is also valid as a normal value, so we need to also check
- # for a raised exception.
- self.emit_line(f"if ({value} == {self.c_error_value(rtype)} && PyErr_Occurred()) {{")
- else:
- self.emit_line(f"if ({value} == {self.c_error_value(rtype)}) {{")
- 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(f"if (CPyTagged_CheckLong({target})) {{")
- self.emit_line(f"Py_VISIT(CPyTagged_LongAsObject({target}));")
- self.emit_line("}")
- elif isinstance(rtype, RTuple):
- for i, item_type in enumerate(rtype.types):
- self.emit_gc_visit(f"{target}.f{i}", item_type)
- elif self.ctype(rtype) == "PyObject *":
- # The simplest case.
- self.emit_line(f"Py_VISIT({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(f"if (CPyTagged_CheckLong({target})) {{")
- self.emit_line(f"CPyTagged __tmp = {target};")
- self.emit_line(f"{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"{target}.f{i}", item_type)
- elif self.ctype(rtype) == "PyObject *" and self.c_undefined_value(rtype) == "NULL":
- # The simplest case.
- self.emit_line(f"Py_CLEAR({target});")
- else:
- assert False, "emit_gc_clear() not implemented for %s" % repr(rtype)
- def emit_traceback(
- self, source_path: str, module_name: str, traceback_entry: tuple[str, int]
- ) -> None:
- return self._emit_traceback("CPy_AddTraceback", source_path, module_name, traceback_entry)
- def emit_type_error_traceback(
- self,
- source_path: str,
- module_name: str,
- traceback_entry: tuple[str, int],
- *,
- typ: RType,
- src: str,
- ) -> None:
- func = "CPy_TypeErrorTraceback"
- type_str = f'"{self.pretty_name(typ)}"'
- return self._emit_traceback(
- func, source_path, module_name, traceback_entry, type_str=type_str, src=src
- )
- def _emit_traceback(
- self,
- func: str,
- source_path: str,
- module_name: str,
- traceback_entry: tuple[str, int],
- type_str: str = "",
- src: str = "",
- ) -> None:
- globals_static = self.static_name("globals", module_name)
- line = '%s("%s", "%s", %d, %s' % (
- func,
- source_path.replace("\\", "\\\\"),
- traceback_entry[0],
- traceback_entry[1],
- globals_static,
- )
- if type_str:
- assert src
- line += f", {type_str}, {src}"
- line += ");"
- self.emit_line(line)
- if DEBUG_ERRORS:
- self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");')
- def emit_unbox_failure_with_overlapping_error_value(
- self, dest: str, typ: RType, failure: str
- ) -> None:
- self.emit_line(f"if ({dest} == {self.c_error_value(typ)} && PyErr_Occurred()) {{")
- self.emit_line(failure)
- self.emit_line("}")
- def c_array_initializer(components: list[str], *, indented: bool = False) -> str:
- """Construct an initializer for a C array variable.
- Components are C expressions valid in an initializer.
- For example, if components are ["1", "2"], the result
- would be "{1, 2}", which can be used like this:
- int a[] = {1, 2};
- If the result is long, split it into multiple lines.
- """
- indent = " " * 4 if indented else ""
- res = []
- current: list[str] = []
- cur_len = 0
- for c in components:
- if not current or cur_len + 2 + len(indent) + len(c) < 70:
- current.append(c)
- cur_len += len(c) + 2
- else:
- res.append(indent + ", ".join(current))
- current = [c]
- cur_len = len(c)
- if not res:
- # Result fits on a single line
- return "{%s}" % ", ".join(current)
- # Multi-line result
- res.append(indent + ", ".join(current))
- return "{\n " + ",\n ".join(res) + "\n" + indent + "}"
|