1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
|
"""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)
):
if src not in init_registers_set:
init_registers.append(src)
init_registers_set.add(src)
# XXX: if src.name is empty, it should be a
# temp... and it should be OK??
if not src.name:
continue
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 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
|