| """Simple copy propagation optimization. |
| |
| Example input: |
| |
| x = f() |
| y = x |
| |
| The register x is redundant and we can directly assign its value to y: |
| |
| y = f() |
| |
| This can optimize away registers that are assigned to once. |
| """ |
| |
| from __future__ import annotations |
| |
| from mypyc.ir.func_ir import FuncIR |
| from mypyc.ir.ops import Assign, AssignMulti, LoadAddress, LoadErrorValue, Register, Value |
| from mypyc.irbuild.ll_builder import LowLevelIRBuilder |
| from mypyc.options import CompilerOptions |
| from mypyc.sametype import is_same_type |
| from mypyc.transform.ir_transform import IRTransform |
| |
| |
| def do_copy_propagation(fn: FuncIR, options: CompilerOptions) -> None: |
| """Perform copy propagation optimization for fn.""" |
| |
| # Anything with an assignment count >1 will not be optimized |
| # here, as it would be require data flow analysis and we want to |
| # keep this simple and fast, at least until we've made data flow |
| # analysis much faster. |
| counts: dict[Value, int] = {} |
| replacements: dict[Value, Value] = {} |
| for arg in fn.arg_regs: |
| # Arguments are always assigned to initially |
| counts[arg] = 1 |
| |
| for block in fn.blocks: |
| for op in block.ops: |
| if isinstance(op, Assign): |
| c = counts.get(op.dest, 0) |
| counts[op.dest] = c + 1 |
| # Does this look like a supported assignment? |
| # TODO: Something needs LoadErrorValue assignments to be preserved? |
| if ( |
| c == 0 |
| and is_same_type(op.dest.type, op.src.type) |
| and not isinstance(op.src, LoadErrorValue) |
| ): |
| replacements[op.dest] = op.src |
| elif c == 1: |
| # Too many assignments -- don't replace this one |
| replacements.pop(op.dest, 0) |
| elif isinstance(op, AssignMulti): |
| # Copy propagation not supported for AssignMulti destinations |
| counts[op.dest] = 2 |
| replacements.pop(op.dest, 0) |
| elif isinstance(op, LoadAddress): |
| # We don't support taking the address of an arbitrary Value, |
| # so we'll need to preserve the operands of LoadAddress. |
| if isinstance(op.src, Register): |
| counts[op.src] = 2 |
| replacements.pop(op.src, 0) |
| |
| # Follow chains of propagation with more than one assignment. |
| for src, dst in list(replacements.items()): |
| if counts.get(dst, 0) > 1: |
| # Not supported |
| del replacements[src] |
| else: |
| while dst in replacements: |
| dst = replacements[dst] |
| if counts.get(dst, 0) > 1: |
| # Not supported |
| del replacements[src] |
| if src in replacements: |
| replacements[src] = dst |
| |
| builder = LowLevelIRBuilder(None, options) |
| transform = CopyPropagationTransform(builder, replacements) |
| transform.transform_blocks(fn.blocks) |
| fn.blocks = builder.blocks |
| |
| |
| class CopyPropagationTransform(IRTransform): |
| def __init__(self, builder: LowLevelIRBuilder, map: dict[Value, Value]) -> None: |
| super().__init__(builder) |
| self.op_map.update(map) |
| self.removed = set(map) |
| |
| def visit_assign(self, op: Assign) -> Value | None: |
| if op.dest in self.removed: |
| return None |
| return self.add(op) |