def EmitCall(fun: ir.Fun, bbl: ir.Bbl, call_ins: ir.Ins, op_stack, mem_base, callee: ir.Fun): """ if the wasm function has the signature: [a b] -> [c d] This means the top of op_stack must be [a b] before the call and will be [c d] after the call The called Cwerg function expects the right most input to be pushed on the stack first, so we get pusharg b pusharg a We always pass mem_base as the the first argument so there is also pusharg mem_base The called Cwerg function pushes the results also from right to left [callee] pusharg d [callee] pusharg c """ # print (f"########## calling {callee.name} in:{callee.input_types} out:{callee.output_types}") # print ("# STACK") # print (op_stack) for dk in reversed(callee.input_types[1:]): arg = op_stack.pop(-1) assert arg.kind == dk, f"expected type {dk} [{arg}] got {arg.kind}" bbl.AddIns(ir.Ins(o.PUSHARG, [arg])) bbl.AddIns(ir.Ins(o.PUSHARG, [mem_base])) bbl.AddIns(call_ins) for dk in callee.output_types: dst = GetOpReg(fun, dk, len(op_stack)) op_stack.append(dst) bbl.AddIns(ir.Ins(o.POPARG, [dst]))
def HandleRotl(dst: ir.Reg, op1: ir.Reg, op2: ir.Reg, bbl: ir.Bbl): assert dst != op1 assert dst.kind is o.DK.U32 or dst.kind is o.DK.U64, f"{dst}" bitwidth = ir.Const(dst.kind, dst.kind.bitwidth()) bbl.AddIns(ir.Ins(o.SHL, [dst, op1, op2])) bbl.AddIns(ir.Ins(o.SUB, [op2, bitwidth, op2])) bbl.AddIns(ir.Ins( o.SHR, [op1, op1, op2])) # here the unsigned requirement kicks in bbl.AddIns(ir.Ins(o.OR, [dst, dst, op1]))
def _BblPropagateDefs(bbl: ir.Bbl, defs_in: ir.REG_DEF_MAP): """ Given a map of definitions that hold at the bbl beginning propagate it to al the ins """ bbl.defs_in = { reg: ins for reg, ins in defs_in.items() if ins is not ir.INS_INVALID } for ins in bbl.inss: # if ins.opcode.is_call(): # callee: ir.Fun = cfg.InsCallee(ins) # assert isinstance(callee, ir.Fun) # for cpu_reg in callee.cpu_live_clobber: # defs_in[cpu_reg] = ir.INS_INVALID # for cpu_reg in callee.cpu_live_out: # defs_in[cpu_reg] = ins num_defs = ins.opcode.def_ops_count() # we need reversed because we want to process the uses before the definitions for n, reg in reversed(list(enumerate(ins.operands))): if not isinstance(reg, ir.Reg): ins.operand_defs[n] = ir.INS_INVALID continue if n < num_defs: defs_in[reg] = ins ins.operand_defs[n] = ir.INS_INVALID else: ins.operand_defs[n] = defs_in[reg]
def _BblRemoveUnreachableIns(bbl: ir.Bbl): n = 0 for n, ins in enumerate(bbl.inss): if ins.opcode.kind in {o.OPC_KIND.RET, o.OPC_KIND.BRA}: break if n != len(bbl.inss): bbl.inss = bbl.inss[:n + 1]
def BblSpillRegs(bbl: ir.Bbl, fun: ir.Bbl, regs: List[ir.Reg], offset_kind: o.DK) -> int: reg_to_stk: Dict[ir.Reg, ir.Stk] = {} for reg in regs: size = ir.OffsetConst(reg.kind.bitwidth() // 8) stk = ir.Stk(f"$spill_{reg.name}", size, size) reg_to_stk[reg] = stk fun.AddStk(stk) ir.BblGenericRewrite(bbl, fun, InsSpillRegs, zero_const=ir.Const(offset_kind, 0), reg_to_stk=reg_to_stk)
def _BblRemoveUselessInstructions(bbl: ir.Bbl, fun: ir.Fun) -> int: live_out = bbl.live_out.copy() old_count = len(bbl.inss) keep = [] for ins in reversed(bbl.inss): if _InsUpdateLiveness(ins, fun, live_out): keep.append(ins) bbl.inss = list(reversed(keep)) return old_count - len(keep)
def FinalizeResultsCopy(self, op_stack, bbl: ir.Bbl, fun: ir.Fun): # print (f"@@ FinalizeCopy {fun.name}: {self.num_results}") dst_pos = self.stack_start + len(self.result_types) src_pos = len(op_stack) for i in range(len(self.result_types)): dst_pos -= 1 src_pos -= 1 op = op_stack[src_pos] dst_reg = GetOpReg(fun, op.kind, dst_pos) if dst_reg != op: bbl.AddIns(ir.Ins(o.MOV, [dst_reg, op]))
def BblSplit(ins: ir.Ins, orig_bbl: ir.Bbl, fun: ir.Fun, prefix) -> ir.Bbl: """Create a new bbl BEFORE orig_bbl containing all the instruction up to and including ins""" assert ins.opcode.kind is not o.OPC_KIND.COND_BRA ins_pos = orig_bbl.index(ins) bbl_pos = fun.bbls.index(orig_bbl) count = 1 while True: name = f"{prefix}{count}" if name not in fun.bbl_syms: break count += 1 new_bbl = ir.Bbl(name) for x in orig_bbl.edge_in[:]: if x.inss: InsMaybePatchNewSuccessor(x.inss[-1], orig_bbl, new_bbl) # patch ins/jtb x.ReplaceEdgeOut(orig_bbl, new_bbl) new_bbl.AddEdgeOut(orig_bbl) new_bbl.inss = orig_bbl.inss[:ins_pos + 1] orig_bbl.inss = orig_bbl.inss[ins_pos + 1:] fun.bbls.insert(bbl_pos, new_bbl) fun.bbl_syms[name] = new_bbl return new_bbl
def _BblMergeMoveWithSrcDef(bbl: ir.Bbl, _fun: ir.Fun) -> int: """ This transformation will make certain MOVs obsolete. op x = a b [stuff] mov y = x will become op y = a b mov x = y [stuff] [deleted] """ last_def_pos: Dict[ir.Reg, int] = {} last_use_pos: Dict[ir.Reg, int] = {} inss: List[ir.Ins] = [] def update_def_use(ins: ir.Ins, pos): num_defs = ins.opcode.def_ops_count() for n, op in enumerate(ins.operands): if not isinstance(op, ir.Reg): continue if n < num_defs: last_def_pos[op] = pos else: last_use_pos[op] = pos def is_suitable_mov(mov: ir.Ins) -> bool: ops = mov.operands if mov.opcode is not o.MOV or not isinstance( ops[1], ir.Reg) or ops[0] == ops[1]: return False src_def_pos = last_def_pos.get(ops[1], -1) if src_def_pos < 0: return False # avoid inserting MOVs inbetween POPARGs - this could be improved if len(inss) > src_def_pos + 1 and inss[src_def_pos + 1].opcode is o.POPARG: return False # no intervening use of ops[0] dst_def_pos = last_def_pos.get(ops[0], -1) if dst_def_pos > src_def_pos: return False dst_use_pos = last_use_pos.get(ops[0], -1) if dst_use_pos > src_def_pos: return False return True count = 0 for ins in bbl.inss: if is_suitable_mov(ins): count += 1 dst_reg, src_reg = ins.operands src_def_pos = last_def_pos[src_reg] ins_src_def = inss[src_def_pos] assert ins_src_def.operands[0] == src_reg ins_src_def.operands[0] = dst_reg last_def_pos[dst_reg] = src_def_pos ir.InsSwapOps(ins, 0, 1) inss.insert(src_def_pos + 1, ins) for pos in range(src_def_pos + 1, len(inss)): update_def_use(inss[pos], pos) else: update_def_use(ins, len(inss)) inss.append(ins) bbl.inss = inss return count
def _InsConstantFold(ins: ir.Ins, bbl: ir.Bbl, _fun: ir.Fun, allow_conv_conversion: bool) -> Optional[List[ir.Ins]]: """ Try combining the constant from ins_def with the instruction in ins Return 1 iff a change was made Note: None of the transformations must change the def register - otherwise the reaching_defs will be stale """ ops = ins.operands kind = ins.opcode.kind if kind is o.OPC_KIND.COND_BRA: if not isinstance(ops[0], ir.Const) or not isinstance( ops[1], ir.Const): return None branch_taken = eval.EvaluatateCondBra(ins.opcode, ops[0], ops[1]) target = ops[2] assert len(bbl.edge_out) == 2 if branch_taken: succ_to_drop = bbl.edge_out[1] if bbl.edge_out[0] == target else \ bbl.edge_out[0] else: succ_to_drop = target bbl.DelEdgeOut(succ_to_drop) return [] elif kind is o.OPC_KIND.CMP: if not isinstance(ops[3], ir.Const) or not isinstance( ops[4], ir.Const): return None cmp_true = eval.EvaluatateCondBra( o.BEQ if ins.opcode is o.CMPEQ else o.BLT, ops[3], ops[4]) if cmp_true: ins.Init(o.MOV, [ops[0], ops[1]]) else: ins.Init(o.MOV, [ops[0], ops[2]]) elif kind is o.OPC_KIND.ALU1: if not isinstance(ops[1], ir.Const): return None new_op = eval.EvaluatateALU1(ins.opcode, ops[1]) ins.Init(o.MOV, [ops[0], new_op]) return [ins] elif kind is o.OPC_KIND.ALU: if not isinstance(ops[1], ir.Const) or not isinstance( ops[2], ir.Const): return None new_op = eval.EvaluatateALU(ins.opcode, ops[1], ops[2]) ins.Init(o.MOV, [ops[0], new_op]) return [ins] elif ins.opcode is o.CONV: # TODO: this needs some more thought generally but in # particular when we apply register widening # transformations, conv instructions end up being the only # ones with narrow width regs which simplifies # code generation. By allowing this to be converted into a # mov instruction we may leak the narrow register. if not allow_conv_conversion or not isinstance(ops[1], ir.Const): return None dst: ir.Reg = ops[0] src = ops[1] if not o.RegIsAddrInt(src.kind) or not o.RegIsAddrInt(dst.kind): return None new_val = eval.ConvertIntValue(dst.kind, src) ins.Init(o.MOV, [dst, new_val]) return [ins] else: return None
def HandleMax(dst: ir.Reg, op1: ir.Reg, op2: ir.Reg, bbl: ir.Bbl): bbl.AddIns(ir.Ins(o.CMPLT, [dst, op2, op1, op1, op2]))
def _InsConstantFold(ins: ir.Ins, bbl: ir.Bbl, _fun: ir.Fun, allow_conv_conversion: bool) -> Optional[List[ir.Ins]]: """ Try combining the constant from ins_def with the instruction in ins Return 1 iff a change was made Note: None of the transformations must change the def register - otherwise the reaching_defs will be stale """ ops = ins.operands kind = ins.opcode.kind if kind is o.OPC_KIND.COND_BRA: if not isinstance(ops[0], ir.Const) or not isinstance( ops[1], ir.Const): return None # TODO: implement this, needs access to BBL for CFG changes evaluator = _EVALUATORS_COND_BRA.get(ins.opcode) assert evaluator, f"Evaluator NYI for: {ins} {ins.operands}" branch_taken = evaluator(ops[0].value, ops[1].value) target = ops[2] assert len(bbl.edge_out) == 2 if branch_taken: succ_to_drop = bbl.edge_out[1] if bbl.edge_out[0] == target else \ bbl.edge_out[0] else: succ_to_drop = target bbl.DelEdgeOut(succ_to_drop) return [] elif kind is o.OPC_KIND.ALU1: if not isinstance(ops[1], ir.Const): return None assert False, f"Evaluator NYI for ALU1: {ins} {ins.operands}" elif kind is o.OPC_KIND.ALU: if not isinstance(ops[1], ir.Const) or not isinstance( ops[2], ir.Const): return None evaluator = _EVALUATORS_ALU.get(ins.opcode) assert evaluator, f"Evaluator NYI for: {ins} {ins.operands}" val = ir.Const(ops[1].kind, evaluator(ops[1].value, ops[2].value)) ins.opcode = o.MOV ins.operands.pop(-1) ins.operands[1] = val ins.operand_defs.pop(-1) ins.operand_defs[1] = ir.INS_INVALID return [ins] elif ins.opcode is o.CONV: # TODO: this needs some more thought generally but in # particular when we apply register widening # transformations, conv instructions end up being the only # ones with narrow width regs which simplifies # code generation. By allowing this to be converted into a # mov instruction we may leak the narrow register. if not allow_conv_conversion or not isinstance(ops[1], ir.Const): return None dst: ir.Reg = ops[0] src = ops[1] if not o.RegIsAddrInt(src.kind) or not o.RegIsAddrInt(dst.kind): return None new_val = ConvertIntValue(dst.kind, src) ins.Init(o.MOV, [dst, new_val]) return [ins] else: return None