Exemple #1
0
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]))
Exemple #2
0
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]))
Exemple #3
0
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]
Exemple #4
0
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]
Exemple #5
0
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)
Exemple #6
0
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)
Exemple #7
0
 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]))
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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
Exemple #11
0
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]))
Exemple #12
0
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