"""Code generation for native function bodies.""" from typing import List, Union, Optional from typing_extensions import Final from mypyc.common import ( REG_PREFIX, NATIVE_PREFIX, STATIC_PREFIX, TYPE_PREFIX, MODULE_PREFIX, ) from mypyc.codegen.emit import Emitter, TracebackAndGotoHandler, DEBUG_ERRORS from mypyc.ir.ops import ( Op, OpVisitor, Goto, Branch, Return, Assign, Integer, LoadErrorValue, GetAttr, SetAttr, LoadStatic, InitStatic, TupleGet, TupleSet, Call, IncRef, DecRef, Box, Cast, Unbox, BasicBlock, Value, MethodCall, Unreachable, NAMESPACE_STATIC, NAMESPACE_TYPE, NAMESPACE_MODULE, RaiseStandardError, CallC, LoadGlobal, Truncate, IntOp, LoadMem, GetElementPtr, LoadAddress, ComparisonOp, SetMem, Register, LoadLiteral, AssignMulti, KeepAlive, Extend, ERR_FALSE ) from mypyc.ir.rtypes import ( RType, RTuple, RArray, is_tagged, is_int32_rprimitive, is_int64_rprimitive, RStruct, is_pointer_rprimitive, is_int_rprimitive ) from mypyc.ir.func_ir import FuncIR, FuncDecl, FUNC_STATICMETHOD, FUNC_CLASSMETHOD, all_values from mypyc.ir.class_ir import ClassIR from mypyc.ir.pprint import generate_names_for_ir from mypyc.analysis.blockfreq import frequently_executed_blocks 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 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: Optional[BasicBlock] = 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 if op.op == Branch.IS_ERROR and isinstance(op.value, GetAttr) and not op.negated: op2 = op.value if op2.class_type.class_ir.is_always_defined(op2.attr): # Getting an always defined attribute never fails, so the branch can be omitted. if false is not self.next_block: self.emit_line('goto {};'.format(self.label(false))) return 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 = '{} {} {}'.format(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: 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) self.emit_lines( 'goto %s;' % self.label(true), '} 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), ', '.join(self.reg(s) for s in op.src))) 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('{} = {};'.format(self.reg(op), self.c_error_value(op.type))) def visit_load_literal(self, op: LoadLiteral) -> None: index = self.literals.literal_index(op.value) s = repr(op.value) if not any(x in s for x in ('/*', '*/', '\0')): ann = ' /* %s */' % s else: ann = '' if not is_int_rprimitive(op.type): self.emit_line('%s = CPyStatics[%d];%s' % (self.reg(op), index, ann)) else: self.emit_line('%s = (CPyTagged)CPyStatics[%d] | 1;%s' % ( self.reg(op), index, ann)) def get_attr_expr(self, obj: str, op: Union[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 '({}{})->{}'.format( 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) if cl.get_method(op.attr): # 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, '==', 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) -> Optional[Branch]: 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 initalization (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, '!=') self.emitter.emit_dec_ref(attr_expr, attr_rtype) if not always_defined: self.emitter.emit_line('}') # 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 ann = '' if op.ann: s = repr(op.ann) if not any(x in s for x in ('/*', '*/', '\0')): ann = ' /* %s */' % s self.emit_line(f'{dest} = {name};{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('{}{}{}{}({});'.format( 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("{} = {}{};".format(dest, src_cast, value)) def visit_load_global(self, op: LoadGlobal) -> None: dest = self.reg(op) ann = '' if op.ann: s = repr(op.ann) if not any(x in s for x in ('/*', '*/', '\0')): ann = ' /* %s */' % s self.emit_line(f'{dest} = {op.identifier};{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('{} = {}{} {} {}{};'.format(dest, lhs_cast, lhs, op.op_str[op.op], rhs_cast, 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) src = self.reg(op.src) if isinstance(op.src, Register) else 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 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) -> None: self.emitter.emit_line(line) 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 ''