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