| """Transform that inserts error checks after opcodes. | |
| When initially building the IR, the code doesn't perform error checks | |
| for exceptions. This module is used to insert all required error checks | |
| afterwards. Each Op describes how it indicates an error condition (if | |
| at all). | |
| We need to split basic blocks on each error check since branches can | |
| only be placed at the end of a basic block. | |
| """ | |
| from __future__ import annotations | |
| from mypyc.ir.func_ir import FuncIR | |
| from mypyc.ir.ops import ( | |
| ERR_ALWAYS, | |
| ERR_FALSE, | |
| ERR_MAGIC, | |
| ERR_MAGIC_OVERLAPPING, | |
| ERR_NEVER, | |
| NO_TRACEBACK_LINE_NO, | |
| BasicBlock, | |
| Branch, | |
| CallC, | |
| ComparisonOp, | |
| Float, | |
| GetAttr, | |
| Integer, | |
| LoadErrorValue, | |
| Op, | |
| RegisterOp, | |
| Return, | |
| SetAttr, | |
| TupleGet, | |
| Value, | |
| ) | |
| from mypyc.ir.rtypes import RTuple, bool_rprimitive, is_float_rprimitive | |
| from mypyc.primitives.exc_ops import err_occurred_op | |
| from mypyc.primitives.registry import CFunctionDescription | |
| def insert_exception_handling(ir: FuncIR, strict_traceback_checks: bool) -> None: | |
| # Generate error block if any ops may raise an exception. If an op | |
| # fails without its own error handler, we'll branch to this | |
| # block. The block just returns an error value. | |
| error_label: BasicBlock | None = None | |
| for block in ir.blocks: | |
| adjust_error_kinds(block) | |
| if error_label is None and any(op.can_raise() for op in block.ops): | |
| error_label = add_default_handler_block(ir) | |
| if error_label: | |
| ir.blocks = split_blocks_at_errors( | |
| ir.blocks, error_label, ir.traceback_name, strict_traceback_checks | |
| ) | |
| def add_default_handler_block(ir: FuncIR) -> BasicBlock: | |
| block = BasicBlock() | |
| ir.blocks.append(block) | |
| op = LoadErrorValue(ir.ret_type) | |
| block.ops.append(op) | |
| block.ops.append(Return(op)) | |
| return block | |
| def split_blocks_at_errors( | |
| blocks: list[BasicBlock], | |
| default_error_handler: BasicBlock, | |
| func_name: str | None, | |
| strict_traceback_checks: bool, | |
| ) -> list[BasicBlock]: | |
| new_blocks: list[BasicBlock] = [] | |
| # First split blocks on ops that may raise. | |
| for block in blocks: | |
| ops = block.ops | |
| block.ops = [] | |
| cur_block = block | |
| new_blocks.append(cur_block) | |
| # If the block has an error handler specified, use it. Otherwise | |
| # fall back to the default. | |
| error_label = block.error_handler or default_error_handler | |
| block.error_handler = None | |
| for op in ops: | |
| target: Value = op | |
| cur_block.ops.append(op) | |
| if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER: | |
| # Split | |
| new_block = BasicBlock() | |
| new_blocks.append(new_block) | |
| if op.error_kind == ERR_MAGIC: | |
| # Op returns an error value on error that depends on result RType. | |
| variant = Branch.IS_ERROR | |
| negated = False | |
| elif op.error_kind == ERR_FALSE: | |
| # Op returns a C false value on error. | |
| variant = Branch.BOOL | |
| negated = True | |
| elif op.error_kind == ERR_ALWAYS: | |
| variant = Branch.BOOL | |
| negated = True | |
| # this is a hack to represent the always fail | |
| # semantics, using a temporary bool with value false | |
| target = Integer(0, bool_rprimitive) | |
| elif op.error_kind == ERR_MAGIC_OVERLAPPING: | |
| comp = insert_overlapping_error_value_check(cur_block.ops, target) | |
| new_block2 = BasicBlock() | |
| new_blocks.append(new_block2) | |
| branch = Branch( | |
| comp, | |
| true_label=new_block2, | |
| false_label=new_block, | |
| op=Branch.BOOL, | |
| rare=True, | |
| ) | |
| cur_block.ops.append(branch) | |
| cur_block = new_block2 | |
| target = primitive_call(err_occurred_op, [], target.line) | |
| cur_block.ops.append(target) | |
| variant = Branch.IS_ERROR | |
| negated = True | |
| else: | |
| assert False, "unknown error kind %d" % op.error_kind | |
| # Void ops can't generate errors since error is always | |
| # indicated by a special value stored in a register. | |
| if op.error_kind != ERR_ALWAYS: | |
| assert not op.is_void, "void op generating errors?" | |
| branch = Branch( | |
| target, true_label=error_label, false_label=new_block, op=variant, line=op.line | |
| ) | |
| branch.negated = negated | |
| if op.line != NO_TRACEBACK_LINE_NO and func_name is not None: | |
| if strict_traceback_checks: | |
| assert ( | |
| op.line >= 0 | |
| ), f"Cannot add a traceback entry with a negative line number for op {op}" | |
| branch.traceback_entry = (func_name, op.line) | |
| cur_block.ops.append(branch) | |
| cur_block = new_block | |
| return new_blocks | |
| def primitive_call(desc: CFunctionDescription, args: list[Value], line: int) -> CallC: | |
| return CallC( | |
| desc.c_function_name, | |
| [], | |
| desc.return_type, | |
| desc.steals, | |
| desc.is_borrowed, | |
| desc.error_kind, | |
| line, | |
| dependencies=desc.dependencies, | |
| ) | |
| def adjust_error_kinds(block: BasicBlock) -> None: | |
| """Infer more precise error_kind attributes for ops. | |
| We have access here to more information than what was available | |
| when the IR was initially built. | |
| """ | |
| for op in block.ops: | |
| if isinstance(op, GetAttr): | |
| if op.class_type.class_ir.is_always_defined(op.attr): | |
| op.error_kind = ERR_NEVER | |
| if isinstance(op, SetAttr): | |
| if op.class_type.class_ir.is_always_defined(op.attr): | |
| op.error_kind = ERR_NEVER | |
| def insert_overlapping_error_value_check(ops: list[Op], target: Value) -> ComparisonOp: | |
| """Append to ops to check for an overlapping error value.""" | |
| typ = target.type | |
| if isinstance(typ, RTuple): | |
| item = TupleGet(target, 0) | |
| ops.append(item) | |
| return insert_overlapping_error_value_check(ops, item) | |
| else: | |
| errvalue: Value | |
| if is_float_rprimitive(target.type): | |
| errvalue = Float(float(typ.c_undefined)) | |
| else: | |
| errvalue = Integer(int(typ.c_undefined), rtype=typ) | |
| op = ComparisonOp(target, errvalue, ComparisonOp.EQ) | |
| ops.append(op) | |
| return op |