| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841 |
- """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 ""
|