"""Code generation for native function bodies.""" from __future__ import annotations from typing import Final from mypyc.analysis.blockfreq import frequently_executed_blocks from mypyc.codegen.emit import DEBUG_ERRORS, Emitter, TracebackAndGotoHandler, c_array_initializer from mypyc.common import MODULE_PREFIX, NATIVE_PREFIX, REG_PREFIX, STATIC_PREFIX, TYPE_PREFIX from mypyc.ir.class_ir import ClassIR from mypyc.ir.func_ir import FUNC_CLASSMETHOD, FUNC_STATICMETHOD, FuncDecl, FuncIR, all_values from mypyc.ir.ops import ( ERR_FALSE, NAMESPACE_MODULE, NAMESPACE_STATIC, NAMESPACE_TYPE, Assign, AssignMulti, BasicBlock, Box, Branch, Call, CallC, Cast, ComparisonOp, ControlOp, DecRef, Extend, Float, FloatComparisonOp, FloatNeg, FloatOp, GetAttr, GetElementPtr, Goto, IncRef, InitStatic, Integer, IntOp, KeepAlive, LoadAddress, LoadErrorValue, LoadGlobal, LoadLiteral, LoadMem, LoadStatic, MethodCall, Op, OpVisitor, RaiseStandardError, Register, Return, SetAttr, SetMem, Truncate, TupleGet, TupleSet, Unbox, Unreachable, Value, ) from mypyc.ir.pprint import generate_names_for_ir from mypyc.ir.rtypes import ( RArray, RStruct, RTuple, RType, is_int32_rprimitive, is_int64_rprimitive, is_int_rprimitive, is_pointer_rprimitive, is_tagged, ) def native_function_type(fn: FuncIR, emitter: Emitter) -> str: args = ", ".join(emitter.ctype(arg.type) for arg in fn.args) or "void" ret = emitter.ctype(fn.ret_type) return f"{ret} (*)({args})" def native_function_header(fn: FuncDecl, emitter: Emitter) -> str: args = [] for arg in fn.sig.args: args.append(f"{emitter.ctype_spaced(arg.type)}{REG_PREFIX}{arg.name}") return "{ret_type}{name}({args})".format( ret_type=emitter.ctype_spaced(fn.sig.ret_type), name=emitter.native_function_name(fn), args=", ".join(args) or "void", ) def generate_native_function( fn: FuncIR, emitter: Emitter, source_path: str, module_name: str ) -> None: declarations = Emitter(emitter.context) names = generate_names_for_ir(fn.arg_regs, fn.blocks) body = Emitter(emitter.context, names) visitor = FunctionEmitterVisitor(body, declarations, source_path, module_name) declarations.emit_line(f"{native_function_header(fn.decl, emitter)} {{") body.indent() for r in all_values(fn.arg_regs, fn.blocks): if isinstance(r.type, RTuple): emitter.declare_tuple_struct(r.type) if isinstance(r.type, RArray): continue # Special: declared on first assignment if r in fn.arg_regs: continue # Skip the arguments ctype = emitter.ctype_spaced(r.type) init = "" declarations.emit_line( "{ctype}{prefix}{name}{init};".format( ctype=ctype, prefix=REG_PREFIX, name=names[r], init=init ) ) # Before we emit the blocks, give them all labels blocks = fn.blocks for i, block in enumerate(blocks): block.label = i # Find blocks that are never jumped to or are only jumped to from the # block directly above it. This allows for more labels and gotos to be # eliminated during code generation. for block in fn.blocks: terminator = block.terminator assert isinstance(terminator, ControlOp) for target in terminator.targets(): is_next_block = target.label == block.label + 1 # Always emit labels for GetAttr error checks since the emit code that # generates them will add instructions between the branch and the # next label, causing the label to be wrongly removed. A better # solution would be to change the IR so that it adds a basic block # inbetween the calls. is_problematic_op = isinstance(terminator, Branch) and any( isinstance(s, GetAttr) for s in terminator.sources() ) if not is_next_block or is_problematic_op: fn.blocks[target.label].referenced = True common = frequently_executed_blocks(fn.blocks[0]) for i in range(len(blocks)): block = blocks[i] visitor.rare = block not in common next_block = None if i + 1 < len(blocks): next_block = blocks[i + 1] body.emit_label(block) visitor.next_block = next_block ops = block.ops visitor.ops = ops visitor.op_index = 0 while visitor.op_index < len(ops): ops[visitor.op_index].accept(visitor) visitor.op_index += 1 body.emit_line("}") emitter.emit_from_emitter(declarations) emitter.emit_from_emitter(body) class FunctionEmitterVisitor(OpVisitor[None]): def __init__( self, emitter: Emitter, declarations: Emitter, source_path: str, module_name: str ) -> None: self.emitter = emitter self.names = emitter.names self.declarations = declarations self.source_path = source_path self.module_name = module_name self.literals = emitter.context.literals self.rare = False # Next basic block to be processed after the current one (if any), set by caller self.next_block: BasicBlock | None = None # Ops in the basic block currently being processed, set by caller self.ops: list[Op] = [] # Current index within ops; visit methods can increment this to skip/merge ops self.op_index = 0 def temp_name(self) -> str: return self.emitter.temp_name() def visit_goto(self, op: Goto) -> None: if op.label is not self.next_block: self.emit_line("goto %s;" % self.label(op.label)) def visit_branch(self, op: Branch) -> None: true, false = op.true, op.false negated = op.negated negated_rare = False if true is self.next_block and op.traceback_entry is None: # Switch true/false since it avoids an else block. true, false = false, true negated = not negated negated_rare = True neg = "!" if negated else "" cond = "" if op.op == Branch.BOOL: expr_result = self.reg(op.value) cond = f"{neg}{expr_result}" elif op.op == Branch.IS_ERROR: typ = op.value.type compare = "!=" if negated else "==" if isinstance(typ, RTuple): # TODO: What about empty tuple? cond = self.emitter.tuple_undefined_check_cond( typ, self.reg(op.value), self.c_error_value, compare ) else: cond = f"{self.reg(op.value)} {compare} {self.c_error_value(typ)}" else: assert False, "Invalid branch" # For error checks, tell the compiler the branch is unlikely if op.traceback_entry is not None or op.rare: if not negated_rare: cond = f"unlikely({cond})" else: cond = f"likely({cond})" if false is self.next_block: if op.traceback_entry is None: if true is not self.next_block: self.emit_line(f"if ({cond}) goto {self.label(true)};") else: self.emit_line(f"if ({cond}) {{") self.emit_traceback(op) self.emit_lines("goto %s;" % self.label(true), "}") else: self.emit_line(f"if ({cond}) {{") self.emit_traceback(op) if true is not self.next_block: self.emit_line("goto %s;" % self.label(true)) self.emit_lines("} else", " goto %s;" % self.label(false)) def visit_return(self, op: Return) -> None: value_str = self.reg(op.value) self.emit_line("return %s;" % value_str) def visit_tuple_set(self, op: TupleSet) -> None: dest = self.reg(op) tuple_type = op.tuple_type self.emitter.declare_tuple_struct(tuple_type) if len(op.items) == 0: # empty tuple self.emit_line(f"{dest}.empty_struct_error_flag = 0;") else: for i, item in enumerate(op.items): self.emit_line(f"{dest}.f{i} = {self.reg(item)};") self.emit_inc_ref(dest, tuple_type) def visit_assign(self, op: Assign) -> None: dest = self.reg(op.dest) src = self.reg(op.src) # clang whines about self assignment (which we might generate # for some casts), so don't emit it. if dest != src: # We sometimes assign from an integer prepresentation of a pointer # to a real pointer, and C compilers insist on a cast. if op.src.type.is_unboxed and not op.dest.type.is_unboxed: src = f"(void *){src}" self.emit_line(f"{dest} = {src};") def visit_assign_multi(self, op: AssignMulti) -> None: typ = op.dest.type assert isinstance(typ, RArray) dest = self.reg(op.dest) # RArray values can only be assigned to once, so we can always # declare them on initialization. self.emit_line( "%s%s[%d] = %s;" % ( self.emitter.ctype_spaced(typ.item_type), dest, len(op.src), c_array_initializer([self.reg(s) for s in op.src], indented=True), ) ) def visit_load_error_value(self, op: LoadErrorValue) -> None: if isinstance(op.type, RTuple): values = [self.c_undefined_value(item) for item in op.type.types] tmp = self.temp_name() self.emit_line("{} {} = {{ {} }};".format(self.ctype(op.type), tmp, ", ".join(values))) self.emit_line(f"{self.reg(op)} = {tmp};") else: self.emit_line(f"{self.reg(op)} = {self.c_error_value(op.type)};") def visit_load_literal(self, op: LoadLiteral) -> None: index = self.literals.literal_index(op.value) if not is_int_rprimitive(op.type): self.emit_line("%s = CPyStatics[%d];" % (self.reg(op), index), ann=op.value) else: self.emit_line( "%s = (CPyTagged)CPyStatics[%d] | 1;" % (self.reg(op), index), ann=op.value ) def get_attr_expr(self, obj: str, op: GetAttr | SetAttr, decl_cl: ClassIR) -> str: """Generate attribute accessor for normal (non-property) access. This either has a form like obj->attr_name for attributes defined in non-trait classes, and *(obj + attr_offset) for attributes defined by traits. We also insert all necessary C casts here. """ cast = f"({op.class_type.struct_name(self.emitter.names)} *)" if decl_cl.is_trait and op.class_type.class_ir.is_trait: # For pure trait access find the offset first, offsets # are ordered by attribute position in the cl.attributes dict. # TODO: pre-calculate the mapping to make this faster. trait_attr_index = list(decl_cl.attributes).index(op.attr) # TODO: reuse these names somehow? offset = self.emitter.temp_name() self.declarations.emit_line(f"size_t {offset};") self.emitter.emit_line( "{} = {};".format( offset, "CPy_FindAttrOffset({}, {}, {})".format( self.emitter.type_struct_name(decl_cl), f"({cast}{obj})->vtable", trait_attr_index, ), ) ) attr_cast = f"({self.ctype(op.class_type.attr_type(op.attr))} *)" return f"*{attr_cast}((char *){obj} + {offset})" else: # Cast to something non-trait. Note: for this to work, all struct # members for non-trait classes must obey monotonic linear growth. if op.class_type.class_ir.is_trait: assert not decl_cl.is_trait cast = f"({decl_cl.struct_name(self.emitter.names)} *)" return f"({cast}{obj})->{self.emitter.attr(op.attr)}" def visit_get_attr(self, op: GetAttr) -> None: dest = self.reg(op) obj = self.reg(op.obj) rtype = op.class_type cl = rtype.class_ir attr_rtype, decl_cl = cl.attr_details(op.attr) prefer_method = cl.is_trait and attr_rtype.error_overlap if cl.get_method(op.attr, prefer_method=prefer_method): # Properties are essentially methods, so use vtable access for them. version = "_TRAIT" if cl.is_trait else "" self.emit_line( "%s = CPY_GET_ATTR%s(%s, %s, %d, %s, %s); /* %s */" % ( dest, version, obj, self.emitter.type_struct_name(rtype.class_ir), rtype.getter_index(op.attr), rtype.struct_name(self.names), self.ctype(rtype.attr_type(op.attr)), op.attr, ) ) else: # Otherwise, use direct or offset struct access. attr_expr = self.get_attr_expr(obj, op, decl_cl) self.emitter.emit_line(f"{dest} = {attr_expr};") always_defined = cl.is_always_defined(op.attr) merged_branch = None if not always_defined: self.emitter.emit_undefined_attr_check( attr_rtype, dest, "==", obj, op.attr, cl, unlikely=True ) branch = self.next_branch() if branch is not None: if ( branch.value is op and branch.op == Branch.IS_ERROR and branch.traceback_entry is not None and not branch.negated ): # Generate code for the following branch here to avoid # redundant branches in the generated code. self.emit_attribute_error(branch, cl.name, op.attr) self.emit_line("goto %s;" % self.label(branch.true)) merged_branch = branch self.emitter.emit_line("}") if not merged_branch: exc_class = "PyExc_AttributeError" self.emitter.emit_line( 'PyErr_SetString({}, "attribute {} of {} undefined");'.format( exc_class, repr(op.attr), repr(cl.name) ) ) if attr_rtype.is_refcounted and not op.is_borrowed: if not merged_branch and not always_defined: self.emitter.emit_line("} else {") self.emitter.emit_inc_ref(dest, attr_rtype) if merged_branch: if merged_branch.false is not self.next_block: self.emit_line("goto %s;" % self.label(merged_branch.false)) self.op_index += 1 elif not always_defined: self.emitter.emit_line("}") def next_branch(self) -> Branch | None: if self.op_index + 1 < len(self.ops): next_op = self.ops[self.op_index + 1] if isinstance(next_op, Branch): return next_op return None def visit_set_attr(self, op: SetAttr) -> None: if op.error_kind == ERR_FALSE: dest = self.reg(op) obj = self.reg(op.obj) src = self.reg(op.src) rtype = op.class_type cl = rtype.class_ir attr_rtype, decl_cl = cl.attr_details(op.attr) if cl.get_method(op.attr): # Again, use vtable access for properties... assert not op.is_init and op.error_kind == ERR_FALSE, "%s %d %d %s" % ( op.attr, op.is_init, op.error_kind, rtype, ) version = "_TRAIT" if cl.is_trait else "" self.emit_line( "%s = CPY_SET_ATTR%s(%s, %s, %d, %s, %s, %s); /* %s */" % ( dest, version, obj, self.emitter.type_struct_name(rtype.class_ir), rtype.setter_index(op.attr), src, rtype.struct_name(self.names), self.ctype(rtype.attr_type(op.attr)), op.attr, ) ) else: # ...and struct access for normal attributes. attr_expr = self.get_attr_expr(obj, op, decl_cl) if not op.is_init and attr_rtype.is_refcounted: # This is not an initialization (where we know that the attribute was # previously undefined), so decref the old value. always_defined = cl.is_always_defined(op.attr) if not always_defined: self.emitter.emit_undefined_attr_check( attr_rtype, attr_expr, "!=", obj, op.attr, cl ) self.emitter.emit_dec_ref(attr_expr, attr_rtype) if not always_defined: self.emitter.emit_line("}") elif attr_rtype.error_overlap and not cl.is_always_defined(op.attr): # If there is overlap with the error value, update bitmap to mark # attribute as defined. self.emitter.emit_attr_bitmap_set(src, obj, attr_rtype, cl, op.attr) # This steals the reference to src, so we don't need to increment the arg self.emitter.emit_line(f"{attr_expr} = {src};") if op.error_kind == ERR_FALSE: self.emitter.emit_line(f"{dest} = 1;") PREFIX_MAP: Final = { NAMESPACE_STATIC: STATIC_PREFIX, NAMESPACE_TYPE: TYPE_PREFIX, NAMESPACE_MODULE: MODULE_PREFIX, } 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 self.emit_line(f"{dest} = {name};", ann=op.ann) def visit_init_static(self, op: InitStatic) -> None: value = self.reg(op.value) prefix = self.PREFIX_MAP[op.namespace] name = self.emitter.static_name(op.identifier, op.module_name, prefix) if op.namespace == NAMESPACE_TYPE: value = "(PyTypeObject *)%s" % value self.emit_line(f"{name} = {value};") self.emit_inc_ref(name, op.value.type) def visit_tuple_get(self, op: TupleGet) -> None: dest = self.reg(op) src = self.reg(op.src) self.emit_line(f"{dest} = {src}.f{op.index};") self.emit_inc_ref(dest, op.type) def get_dest_assign(self, dest: Value) -> str: if not dest.is_void: return self.reg(dest) + " = " else: return "" def visit_call(self, op: Call) -> None: """Call native function.""" dest = self.get_dest_assign(op) args = ", ".join(self.reg(arg) for arg in op.args) lib = self.emitter.get_group_prefix(op.fn) cname = op.fn.cname(self.names) self.emit_line(f"{dest}{lib}{NATIVE_PREFIX}{cname}({args});") def visit_method_call(self, op: MethodCall) -> None: """Call native method.""" dest = self.get_dest_assign(op) obj = self.reg(op.obj) rtype = op.receiver_type class_ir = rtype.class_ir name = op.method method = rtype.class_ir.get_method(name) assert method is not None # Can we call the method directly, bypassing vtable? is_direct = class_ir.is_method_final(name) # The first argument gets omitted for static methods and # turned into the class for class methods obj_args = ( [] if method.decl.kind == FUNC_STATICMETHOD else [f"(PyObject *)Py_TYPE({obj})"] if method.decl.kind == FUNC_CLASSMETHOD else [obj] ) args = ", ".join(obj_args + [self.reg(arg) for arg in op.args]) mtype = native_function_type(method, self.emitter) version = "_TRAIT" if rtype.class_ir.is_trait else "" if is_direct: # Directly call method, without going through the vtable. lib = self.emitter.get_group_prefix(method.decl) self.emit_line(f"{dest}{lib}{NATIVE_PREFIX}{method.cname(self.names)}({args});") else: # Call using vtable. method_idx = rtype.method_index(name) self.emit_line( "{}CPY_GET_METHOD{}({}, {}, {}, {}, {})({}); /* {} */".format( dest, version, obj, self.emitter.type_struct_name(rtype.class_ir), method_idx, rtype.struct_name(self.names), mtype, args, op.method, ) ) def visit_inc_ref(self, op: IncRef) -> None: src = self.reg(op.src) self.emit_inc_ref(src, op.src.type) def visit_dec_ref(self, op: DecRef) -> None: src = self.reg(op.src) self.emit_dec_ref(src, op.src.type, is_xdec=op.is_xdec) def visit_box(self, op: Box) -> None: self.emitter.emit_box(self.reg(op.src), self.reg(op), op.src.type, can_borrow=True) def visit_cast(self, op: Cast) -> None: branch = self.next_branch() handler = None if branch is not None: if ( branch.value is op and branch.op == Branch.IS_ERROR and branch.traceback_entry is not None and not branch.negated and branch.false is self.next_block ): # Generate code also for the following branch here to avoid # redundant branches in the generated code. handler = TracebackAndGotoHandler( self.label(branch.true), self.source_path, self.module_name, branch.traceback_entry, ) self.op_index += 1 self.emitter.emit_cast( self.reg(op.src), self.reg(op), op.type, src_type=op.src.type, error=handler ) def visit_unbox(self, op: Unbox) -> None: self.emitter.emit_unbox(self.reg(op.src), self.reg(op), op.type) def visit_unreachable(self, op: Unreachable) -> None: self.emitter.emit_line("CPy_Unreachable();") def visit_raise_standard_error(self, op: RaiseStandardError) -> None: # TODO: Better escaping of backspaces and such if op.value is not None: if isinstance(op.value, str): message = op.value.replace('"', '\\"') self.emitter.emit_line(f'PyErr_SetString(PyExc_{op.class_name}, "{message}");') elif isinstance(op.value, Value): self.emitter.emit_line( "PyErr_SetObject(PyExc_{}, {});".format( op.class_name, self.emitter.reg(op.value) ) ) else: assert False, "op value type must be either str or Value" else: self.emitter.emit_line(f"PyErr_SetNone(PyExc_{op.class_name});") self.emitter.emit_line(f"{self.reg(op)} = 0;") def visit_call_c(self, op: CallC) -> None: if op.is_void: dest = "" else: dest = self.get_dest_assign(op) args = ", ".join(self.reg(arg) for arg in op.args) self.emitter.emit_line(f"{dest}{op.function_name}({args});") def visit_truncate(self, op: Truncate) -> None: dest = self.reg(op) value = self.reg(op.src) # for C backend the generated code are straight assignments self.emit_line(f"{dest} = {value};") def visit_extend(self, op: Extend) -> None: dest = self.reg(op) value = self.reg(op.src) if op.signed: src_cast = self.emit_signed_int_cast(op.src.type) else: src_cast = self.emit_unsigned_int_cast(op.src.type) self.emit_line(f"{dest} = {src_cast}{value};") def visit_load_global(self, op: LoadGlobal) -> None: dest = self.reg(op) self.emit_line(f"{dest} = {op.identifier};", ann=op.ann) def visit_int_op(self, op: IntOp) -> None: dest = self.reg(op) lhs = self.reg(op.lhs) rhs = self.reg(op.rhs) if op.op == IntOp.RIGHT_SHIFT: # Signed right shift lhs = self.emit_signed_int_cast(op.lhs.type) + lhs rhs = self.emit_signed_int_cast(op.rhs.type) + rhs self.emit_line(f"{dest} = {lhs} {op.op_str[op.op]} {rhs};") def visit_comparison_op(self, op: ComparisonOp) -> None: dest = self.reg(op) lhs = self.reg(op.lhs) rhs = self.reg(op.rhs) lhs_cast = "" rhs_cast = "" if op.op in (ComparisonOp.SLT, ComparisonOp.SGT, ComparisonOp.SLE, ComparisonOp.SGE): # Always signed comparison op lhs_cast = self.emit_signed_int_cast(op.lhs.type) rhs_cast = self.emit_signed_int_cast(op.rhs.type) elif op.op in (ComparisonOp.ULT, ComparisonOp.UGT, ComparisonOp.ULE, ComparisonOp.UGE): # Always unsigned comparison op lhs_cast = self.emit_unsigned_int_cast(op.lhs.type) rhs_cast = self.emit_unsigned_int_cast(op.rhs.type) elif isinstance(op.lhs, Integer) and op.lhs.value < 0: # Force signed ==/!= with negative operand rhs_cast = self.emit_signed_int_cast(op.rhs.type) elif isinstance(op.rhs, Integer) and op.rhs.value < 0: # Force signed ==/!= with negative operand lhs_cast = self.emit_signed_int_cast(op.lhs.type) self.emit_line(f"{dest} = {lhs_cast}{lhs} {op.op_str[op.op]} {rhs_cast}{rhs};") def visit_float_op(self, op: FloatOp) -> None: dest = self.reg(op) lhs = self.reg(op.lhs) rhs = self.reg(op.rhs) if op.op != FloatOp.MOD: self.emit_line(f"{dest} = {lhs} {op.op_str[op.op]} {rhs};") else: # TODO: This may set errno as a side effect, that is a little sketchy. self.emit_line(f"{dest} = fmod({lhs}, {rhs});") def visit_float_neg(self, op: FloatNeg) -> None: dest = self.reg(op) src = self.reg(op.src) self.emit_line(f"{dest} = -{src};") def visit_float_comparison_op(self, op: FloatComparisonOp) -> None: dest = self.reg(op) lhs = self.reg(op.lhs) rhs = self.reg(op.rhs) self.emit_line(f"{dest} = {lhs} {op.op_str[op.op]} {rhs};") def visit_load_mem(self, op: LoadMem) -> None: dest = self.reg(op) src = self.reg(op.src) # TODO: we shouldn't dereference to type that are pointer type so far type = self.ctype(op.type) self.emit_line(f"{dest} = *({type} *){src};") def visit_set_mem(self, op: SetMem) -> None: dest = self.reg(op.dest) src = self.reg(op.src) dest_type = self.ctype(op.dest_type) # clang whines about self assignment (which we might generate # for some casts), so don't emit it. if dest != src: self.emit_line(f"*({dest_type} *){dest} = {src};") def visit_get_element_ptr(self, op: GetElementPtr) -> None: dest = self.reg(op) src = self.reg(op.src) # TODO: support tuple type assert isinstance(op.src_type, RStruct) assert op.field in op.src_type.names, "Invalid field name." self.emit_line( "{} = ({})&(({} *){})->{};".format( dest, op.type._ctype, op.src_type.name, src, op.field ) ) def visit_load_address(self, op: LoadAddress) -> None: typ = op.type dest = self.reg(op) if isinstance(op.src, Register): src = self.reg(op.src) elif isinstance(op.src, LoadStatic): prefix = self.PREFIX_MAP[op.src.namespace] src = self.emitter.static_name(op.src.identifier, op.src.module_name, prefix) else: src = op.src self.emit_line(f"{dest} = ({typ._ctype})&{src};") def visit_keep_alive(self, op: KeepAlive) -> None: # This is a no-op. pass # Helpers def label(self, label: BasicBlock) -> str: return self.emitter.label(label) def reg(self, reg: Value) -> str: if isinstance(reg, Integer): val = reg.value if val == 0 and is_pointer_rprimitive(reg.type): return "NULL" s = str(val) if val >= (1 << 31): # Avoid overflowing signed 32-bit int if val >= (1 << 63): s += "ULL" else: s += "LL" elif val == -(1 << 63): # Avoid overflowing C integer literal s = "(-9223372036854775807LL - 1)" elif val <= -(1 << 31): s += "LL" return s elif isinstance(reg, Float): r = repr(reg.value) if r == "inf": return "INFINITY" elif r == "-inf": return "-INFINITY" elif r == "nan": return "NAN" return r else: return self.emitter.reg(reg) def ctype(self, rtype: RType) -> str: return self.emitter.ctype(rtype) def c_error_value(self, rtype: RType) -> str: return self.emitter.c_error_value(rtype) def c_undefined_value(self, rtype: RType) -> str: return self.emitter.c_undefined_value(rtype) def emit_line(self, line: str, *, ann: object = None) -> None: self.emitter.emit_line(line, ann=ann) def emit_lines(self, *lines: str) -> None: self.emitter.emit_lines(*lines) def emit_inc_ref(self, dest: str, rtype: RType) -> None: self.emitter.emit_inc_ref(dest, rtype, rare=self.rare) def emit_dec_ref(self, dest: str, rtype: RType, is_xdec: bool) -> None: self.emitter.emit_dec_ref(dest, rtype, is_xdec=is_xdec, rare=self.rare) def emit_declaration(self, line: str) -> None: self.declarations.emit_line(line) def emit_traceback(self, op: Branch) -> None: if op.traceback_entry is not None: self.emitter.emit_traceback(self.source_path, self.module_name, op.traceback_entry) def emit_attribute_error(self, op: Branch, class_name: str, attr: str) -> None: assert op.traceback_entry is not None globals_static = self.emitter.static_name("globals", self.module_name) self.emit_line( 'CPy_AttributeError("%s", "%s", "%s", "%s", %d, %s);' % ( self.source_path.replace("\\", "\\\\"), op.traceback_entry[0], class_name, attr, op.traceback_entry[1], globals_static, ) ) if DEBUG_ERRORS: self.emit_line('assert(PyErr_Occurred() != NULL && "failure w/o err!");') def emit_signed_int_cast(self, type: RType) -> str: if is_tagged(type): return "(Py_ssize_t)" else: return "" def emit_unsigned_int_cast(self, type: RType) -> str: if is_int32_rprimitive(type): return "(uint32_t)" elif is_int64_rprimitive(type): return "(uint64_t)" else: return ""