| """Code generation for native function bodies.""" | |
| from __future__ import annotations | |
| from typing_extensions 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("%s = %s %s %s;" % (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("%s = fmod(%s, %s);" % (dest, 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("%s = %s %s %s;" % (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 "" |