def _InsUpdateDefUse(ins: ir.Ins, fun: ir.Fun, defs: Set[ir.Reg], uses: Set[ir.Reg]): """Compute the set of defined and used registers for each instruction""" if ins.opcode.is_call(): # note: a call instruction may have at most one used reg if opcode is JSR callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) for cpu_reg in callee.cpu_live_out: # we intentionally iterate overall regs here for reg in fun.regs: if reg.cpu_reg is cpu_reg: defs.add(reg) uses.discard(reg) # for cpu_reg in callee.cpu_live_clobber: # defs.add(cpu_reg) # uses.discard(cpu_reg) # for cpu_reg in callee.cpu_live_in: # uses.add(cpu_reg) num_defs = ins.opcode.def_ops_count() for n, reg in enumerate(ins.operands): if not isinstance(reg, ir.Reg): continue if n < num_defs: defs.add(reg) uses.discard(reg) else: uses.add(reg)
def handle_call(self, ins, bbl, fun): callee: ir.Fun = cfg.InsCallee(ins) assert not self.pop_args, (f"unconsumed popargs from {self.callee} " f"in {fun.name}:{bbl.name}: {self.pop_args}") self.pop_args = callee.output_types.copy() self.callee = f"{callee.name} results" self.push_args.reverse() assert self.push_args == callee.input_types, (f"parameter mismatch for {callee.name} " f"in {fun.name}:{bbl.name} " f"actual:{self.push_args} vs expected:{callee.input_types}") self.push_args = []
def _InsPushargConversionReverse(ins: ir.Ins, fun: ir.Fun, params: List[ir.CpuReg]) -> Optional[ List[ir.Ins]]: """ This pass converts pusharg reg -> mov arg_reg = reg Note: * params is passed around between calls to this function * pusharg's always precede calls or returns """ if ins.opcode is o.PUSHARG: cpu_reg = params.pop(0) src = ins.operands[0] reg = fun.FindOrAddCpuReg(cpu_reg, src.kind) return [ir.Ins(o.MOV, [reg, src])] assert not params, f"params {params} should be empty at ins {ins} {ins.operands}" if ins.opcode.is_call(): callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) params += GetCpuRegsForSignature(callee.input_types) elif ins.opcode is o.RET: params += GetCpuRegsForSignature(fun.output_types) return None
def _InsPopargConversion(ins: ir.Ins, fun: ir.Fun, params: List[ir.CpuReg]) -> Optional[List[ir.Ins]]: """ This pass converts `poparg reg` -> `mov reg = arg_reg` it must used in a forward pass over the Bbl and will update `param` for use with the next Ins in the BBl. The initial value of `param` reflects the Fun's arguments. """ if ins.opcode is o.POPARG: cpu_reg = params.pop(0) dst = ins.operands[0] # assert dst.kind == cpu_reg.kind reg = fun.FindOrAddCpuReg(cpu_reg, dst.kind) return [ir.Ins(o.MOV, [dst, reg])] assert not params, f"params {params} should be empty at ins {ins}" if ins.opcode.is_call(): callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) params += GetCpuRegsForSignature(callee.output_types) return None
def _InsUpdateLiveness(ins: ir.Ins, fun: ir.Fun, live_out: Set[ir.Reg]) -> bool: """Similar to _InsUpdateDefUse but also checks if the instruction is useless""" if ins.opcode.is_call(): # note: a call instruction may have at most one used reg if opcode is JSR callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) for cpu_reg in callee.cpu_live_out: for reg in fun.regs: if reg.cpu_reg is cpu_reg: live_out.discard(reg) # TODO: take cpu_live_in into account, otherwise we cannot eliminate useless code # after pusharg and poparg convdersions # live_out.discard(cpu_reg) # for cpu_reg in callee.cpu_live_clobber: # live_out.discard(cpu_reg) # for cpu_reg in callee.cpu_live_in: # live_out.add(cpu_reg) is_live = ins.opcode.has_side_effect() num_defs = ins.opcode.def_ops_count() for n, reg in enumerate(ins.operands): if not isinstance(reg, ir.Reg): continue if n < num_defs: if reg in live_out: is_live = True live_out.discard(reg) else: # all defs precede the uses, so if the ins is not live at this point, # we can skip the if not is_live: break live_out.add(reg) return is_live
def BblGetLiveRanges(bbl: ir.Bbl, fun: ir.Fun, live_out: Set[ir.Reg], emit_uses: bool) -> List[LiveRange]: """ LiveRanges are use to do register allocation Note: function call handling is quite adhoc and likely has bugs """ out = [] bbl_size = len(bbl.inss) last_use: Dict[ir.Reg, LiveRange] = {} last_call_pos = -1 last_call_cpu_live_in = [] def initialize_lr(pos: int, reg: ir.Reg) -> LiveRange: lr = LiveRange(-1, pos, reg, 1) last_use[reg] = lr out.append(lr) return lr def finalize_lr(lr: LiveRange, def_pos: int): lr.def_pos = def_pos if (last_call_pos != -1 and last_call_pos != AFTER_BBL and last_call_pos < lr.last_use_pos): lr.flags |= LiveRangeFlag.LAC del last_use[lr.reg] # handle live ranges that extend passed the bbl for reg in live_out: initialize_lr(AFTER_BBL, reg) for pos, ins in enumerate(reversed(bbl.inss)): pos = bbl_size - 1 - pos if ins.opcode is o.RET: if fun.cpu_live_out: last_call_cpu_live_in = fun.cpu_live_out last_call_pos = AFTER_BBL elif ins.opcode.is_call(): callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) # This complication only applies after we have (partial) reg allocation # Finalize live ranges using the results of the call if callee.cpu_live_out: # Note, destructive list iteration -> `list(...)` is necessary for reg, lr in list(last_use.items()): if reg.HasCpuReg() and reg.cpu_reg in callee.cpu_live_out: finalize_lr(lr, pos) last_call_cpu_live_in = callee.cpu_live_in last_call_pos = pos # setting this after dealing with cpu_live_out seems right num_defs = ins.opcode.def_ops_count() uses = [] for n, reg in enumerate(ins.operands): if not isinstance(reg, ir.Reg): continue if n < num_defs: lr = last_use.get(reg) if lr: finalize_lr(lr, pos) else: last_use_pos = NO_USE # Note: likely this makes some assumptions about the adjacency # of these instruction and the call: We assume this cannot be LAC! if reg.HasCpuReg( ) and reg.cpu_reg in last_call_cpu_live_in: last_use_pos = last_call_pos out.append(LiveRange(pos, last_use_pos, reg, 0)) else: lr = last_use.get(reg) if lr: lr.num_uses += 1 else: lr = initialize_lr(pos, reg) if lr not in uses: uses.append(lr) if emit_uses and uses: # Note "pos, pos" ensure that this record will come before # a regular record after sorting out.append(LiveRange(pos, pos, ir.REG_INVALID, 0, uses)) for lr in list(last_use.values()): finalize_lr(lr, BEFORE_BBL) return out
def BblGetLiveRanges(bbl: ir.Bbl, fun: ir.Fun, live_out: Set[ir.Reg]) -> List[LiveRange]: """ LiveRanges are use to do register allocation Note: function call handling is quite adhoc and likely has bugs. The output contains the following special LiveRanges * LRs without a last_use if the register is used outside the Bbl (based on live_out) * LRs without a def of the register is defined outside the Bbl * use-def LRs contain the LRs of all the used regs in the instruction at point p. (def=p last_use=p, reg=REG_INVALID) """ out = [] bbl_size = len(bbl.inss) last_use: Dict[ir.Reg, LiveRange] = {} last_call_pos = -1 last_call_cpu_live_in = [] def initialize_lr(pos: int, reg: ir.Reg) -> LiveRange: lr = LiveRange(-1, pos, reg, 1) last_use[reg] = lr out.append(lr) return lr def finalize_lr(lr: LiveRange, def_pos: int): lr.def_pos = def_pos if (last_call_pos != -1 and last_call_pos != AFTER_BBL and last_call_pos < lr.last_use_pos): lr.flags |= LiveRangeFlag.LAC del last_use[lr.reg] # handle live ranges that extend passed the bbl for reg in live_out: if reg.IsSpilled(): continue initialize_lr(AFTER_BBL, reg) for pos, ins in enumerate(reversed(bbl.inss)): pos = bbl_size - 1 - pos if ins.opcode is o.RET: if fun.cpu_live_out: last_call_cpu_live_in = fun.cpu_live_out last_call_pos = AFTER_BBL elif ins.opcode.is_call(): callee: ir.Fun = cfg.InsCallee(ins) assert isinstance(callee, ir.Fun) # This complication only applies after we have (partial) reg allocation # Finalize live ranges using the results of the call if callee.cpu_live_out: # Note, destructive list iteration -> `list(...)` is necessary for reg, lr in list(last_use.items()): if reg.HasCpuReg() and reg.cpu_reg in callee.cpu_live_out: finalize_lr(lr, pos) last_call_cpu_live_in = callee.cpu_live_in last_call_pos = pos # setting this after dealing with cpu_live_out seems right num_defs = ins.opcode.def_ops_count() uses = [] for n, reg in enumerate(ins.operands): if not isinstance(reg, ir.Reg): continue if reg.IsSpilled(): continue if n < num_defs: # define reg if n == 0 and ir.REG_FLAG.TWO_ADDRESS in reg.flags and reg == ins.operands[ 1]: continue lr = last_use.get(reg) if lr: finalize_lr(lr, pos) else: last_use_pos = NO_USE # Note: likely this makes some assumptions about the adjacency # of these instruction and the call: We assume this cannot be LAC! if reg.HasCpuReg( ) and reg.cpu_reg in last_call_cpu_live_in: last_use_pos = last_call_pos #elif ins.opcode is o.NOP1: # # assert False, f"found nop1 {ins.operands}" # last_use_pos = n - 1 out.append(LiveRange(pos, last_use_pos, reg, 0)) else: # used reg lr = last_use.get(reg) if lr: # make meaning of num_uses more precise lr.num_uses += 1 else: lr = initialize_lr(pos, reg) if lr not in uses: uses.append(lr) if uses: # Note "pos, pos" ensure that this record will come before # a regular record after sorting out.append(LiveRange(pos, pos, ir.REG_INVALID, 0, uses)) for lr in list(last_use.values()): finalize_lr(lr, BEFORE_BBL) return out