| """Insert checks for uninitialized values.""" |
| |
| from __future__ import annotations |
| |
| from mypyc.analysis.dataflow import AnalysisDict, analyze_must_defined_regs, cleanup_cfg, get_cfg |
| from mypyc.common import BITMAP_BITS |
| from mypyc.ir.func_ir import FuncIR, all_values |
| from mypyc.ir.ops import ( |
| Assign, |
| BasicBlock, |
| Branch, |
| ComparisonOp, |
| Integer, |
| IntOp, |
| LoadAddress, |
| LoadErrorValue, |
| Op, |
| RaiseStandardError, |
| Register, |
| Unreachable, |
| Value, |
| ) |
| from mypyc.ir.rtypes import bitmap_rprimitive |
| |
| |
| def insert_uninit_checks(ir: FuncIR) -> None: |
| # Remove dead blocks from the CFG, which helps avoid spurious |
| # checks due to unused error handling blocks. |
| cleanup_cfg(ir.blocks) |
| |
| cfg = get_cfg(ir.blocks) |
| must_defined = analyze_must_defined_regs( |
| ir.blocks, cfg, set(ir.arg_regs), all_values(ir.arg_regs, ir.blocks) |
| ) |
| |
| ir.blocks = split_blocks_at_uninits(ir.blocks, must_defined.before) |
| |
| |
| def split_blocks_at_uninits( |
| blocks: list[BasicBlock], pre_must_defined: AnalysisDict[Value] |
| ) -> list[BasicBlock]: |
| new_blocks: list[BasicBlock] = [] |
| |
| init_registers = [] |
| init_registers_set = set() |
| bitmap_registers: list[Register] = [] # Init status bitmaps |
| bitmap_backed: list[Register] = [] # These use bitmaps to track init status |
| |
| # 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) |
| |
| for i, op in enumerate(ops): |
| defined = pre_must_defined[block, i] |
| for src in op.unique_sources(): |
| # If a register operand is not guaranteed to be |
| # initialized is an operand to something other than a |
| # check that it is defined, insert a check. |
| |
| # Note that for register operand in a LoadAddress op, |
| # we should be able to use it without initialization |
| # as we may need to use its address to update itself |
| if ( |
| isinstance(src, Register) |
| and src not in defined |
| and not (isinstance(op, Branch) and op.op == Branch.IS_ERROR) |
| and not isinstance(op, LoadAddress) |
| ): |
| new_block, error_block = BasicBlock(), BasicBlock() |
| new_block.error_handler = error_block.error_handler = cur_block.error_handler |
| new_blocks += [error_block, new_block] |
| |
| if src not in init_registers_set: |
| init_registers.append(src) |
| init_registers_set.add(src) |
| |
| if not src.type.error_overlap: |
| cur_block.ops.append( |
| Branch( |
| src, |
| true_label=error_block, |
| false_label=new_block, |
| op=Branch.IS_ERROR, |
| line=op.line, |
| ) |
| ) |
| else: |
| # We need to use bitmap for this one. |
| check_for_uninit_using_bitmap( |
| cur_block.ops, |
| src, |
| bitmap_registers, |
| bitmap_backed, |
| error_block, |
| new_block, |
| op.line, |
| ) |
| |
| raise_std = RaiseStandardError( |
| RaiseStandardError.UNBOUND_LOCAL_ERROR, |
| f'local variable "{src.name}" referenced before assignment', |
| op.line, |
| ) |
| error_block.ops.append(raise_std) |
| error_block.ops.append(Unreachable()) |
| cur_block = new_block |
| cur_block.ops.append(op) |
| |
| if bitmap_backed: |
| update_register_assignments_to_set_bitmap(new_blocks, bitmap_registers, bitmap_backed) |
| |
| if init_registers: |
| new_ops: list[Op] = [] |
| for reg in init_registers: |
| err = LoadErrorValue(reg.type, undefines=True) |
| new_ops.append(err) |
| new_ops.append(Assign(reg, err)) |
| for reg in bitmap_registers: |
| new_ops.append(Assign(reg, Integer(0, bitmap_rprimitive))) |
| new_blocks[0].ops[0:0] = new_ops |
| |
| return new_blocks |
| |
| |
| def check_for_uninit_using_bitmap( |
| ops: list[Op], |
| src: Register, |
| bitmap_registers: list[Register], |
| bitmap_backed: list[Register], |
| error_block: BasicBlock, |
| ok_block: BasicBlock, |
| line: int, |
| ) -> None: |
| """Check if src is defined using a bitmap. |
| |
| Modifies ops, bitmap_registers and bitmap_backed. |
| """ |
| if src not in bitmap_backed: |
| # Set up a new bitmap backed register. |
| bitmap_backed.append(src) |
| n = (len(bitmap_backed) - 1) // BITMAP_BITS |
| if len(bitmap_registers) <= n: |
| bitmap_registers.append(Register(bitmap_rprimitive, f"__locals_bitmap{n}")) |
| |
| index = bitmap_backed.index(src) |
| masked = IntOp( |
| bitmap_rprimitive, |
| bitmap_registers[index // BITMAP_BITS], |
| Integer(1 << (index & (BITMAP_BITS - 1)), bitmap_rprimitive), |
| IntOp.AND, |
| line, |
| ) |
| ops.append(masked) |
| chk = ComparisonOp(masked, Integer(0, bitmap_rprimitive), ComparisonOp.EQ) |
| ops.append(chk) |
| ops.append(Branch(chk, error_block, ok_block, Branch.BOOL)) |
| |
| |
| def update_register_assignments_to_set_bitmap( |
| blocks: list[BasicBlock], bitmap_registers: list[Register], bitmap_backed: list[Register] |
| ) -> None: |
| """Update some assignments to registers to also set a bit in a bitmap. |
| |
| The bitmaps are used to track if a local variable has been assigned to. |
| |
| Modifies blocks. |
| """ |
| for block in blocks: |
| if any(isinstance(op, Assign) and op.dest in bitmap_backed for op in block.ops): |
| new_ops: list[Op] = [] |
| for op in block.ops: |
| if isinstance(op, Assign) and op.dest in bitmap_backed: |
| index = bitmap_backed.index(op.dest) |
| new_ops.append(op) |
| reg = bitmap_registers[index // BITMAP_BITS] |
| new = IntOp( |
| bitmap_rprimitive, |
| reg, |
| Integer(1 << (index & (BITMAP_BITS - 1)), bitmap_rprimitive), |
| IntOp.OR, |
| op.line, |
| ) |
| new_ops.append(new) |
| new_ops.append(Assign(reg, new)) |
| else: |
| new_ops.append(op) |
| block.ops = new_ops |