/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */
#define RCSID5 "$Id$"

/*
 * INTEL 80386 special routines
 */

void ea_1_16(int param)
{
        reg_1 &= 0377;
        if (param & ~070) {
                serror("bad operand");
        }
        emit1(reg_1 | param);
        switch(reg_1 >> 6) {
        case 0:
                if (reg_1 == 6 || (reg_1 & 040)) {
#ifdef RELOCATION
                        RELOMOVE(relonami, rel_1);
                        newrelo(exp_1.typ, RELO2);
#endif
                        emit2(exp_1.val);
                }
                break;
        case 1:
                emit1(exp_1.val);
                break;
        case 2:
#ifdef RELOCATION
                RELOMOVE(relonami, rel_1);
                newrelo(exp_1.typ, RELO2);
#endif
                emit2(exp_1.val);
                break;
        }
}

void ea_1(int param) {
        if (! address_long) {
                ea_1_16(param);
                return;
        }
        if (is_expr(reg_1)) {
                serror("bad operand");
                return;
        }
        if (is_reg(reg_1)) {
                emit1(0300 | param | (reg_1&07));
                return;
        }
        if (rm_1 == 04) {
                /* sib field use here */
                emit1(mod_1 << 6 | param | 04);
                emit1(sib_1 | reg_1);
        }
        else emit1(mod_1<<6 | param | (reg_1&07));
        if ((mod_1 == 0 && reg_1 == 5) || mod_1 == 2) {
                /* ??? should this be protected by a call to "small" ??? */
#ifdef RELOCATION
                RELOMOVE(relonami, rel_1);
                newrelo(exp_1.typ, RELO4);
#endif
                emit4((long)(exp_1.val));
        }
        else if (mod_1 == 1) {
                emit1((int)(exp_1.val));
        }
}

void ea_2(int param) {

        op_1 = op_2;
        RELOMOVE(rel_1, rel_2);
        ea_1(param);
}

int checkscale(valu_t val)
{
        int v = val;

        if (! address_long) {
                serror("scaling not allowed in 16-bit mode");
                return 0;
        }
        if (v != val) v = 0;
        switch(v) {
        case 1:
                return 0;
        case 2:
                return 1 << 6;
        case 4:
                return 2 << 6;
        case 8:
                return 3 << 6;
        default:
                serror("bad scale");
                return 0;
        }
        /*NOTREACHED*/
}

void reverse(void) {
        struct operand op;
#ifdef RELOCATION
        int r = rel_1;

        rel_1 = rel_2; rel_2 = r;
#endif
        op = op_1; op_1 = op_2; op_2 = op;
}

void badsyntax(void)
{

        serror("bad operands");
}

void regsize(int sz)
{
        register int bit;

        bit = (sz&1) ? 0 : IS_R8;
        if ((is_reg(reg_1) && (reg_1 & IS_R8) != bit) ||
            (is_reg(reg_2) && (reg_2 & IS_R8) != bit)) 
                serror("register error");
        if (! address_long) {
                reg_1 &= ~010;
                reg_2 &= ~010;
        }
}

void indexed(void) {
        if (address_long) {
                mod_2 = 0;
                if (sib_2 == -1)
                        serror("register error");
                if (rm_2 == 0 && reg_2 == 4) {
                        /* base register sp, no index register; use
                           indexed mode without index register
                        */
                        rm_2 = 04;
                        sib_2 = 044;
                }
                if (reg_2 == 015) {
                        reg_2 = 05;
                        return;
                }
                if (small(exp_2.typ == S_ABS && fitb(exp_2.val), 3)) {
                        if (small(exp_2.val == 0 && reg_2 != 5, 1)) {
                        }
                        else mod_2 = 01;
                }
                else    if (small(0, 1)) {
                }
                else mod_2 = 02;
        }
        else {
                if (reg_2 & ~7)
                        serror("register error");
                if (small(exp_2.typ == S_ABS && fitb(exp_2.val), 1)) {
                        if (small(exp_2.val == 0 && reg_2 != 6, 1)) {
                        }
                        else reg_2 |= 0100;
                }
                else if (small(0, 1)) {
                }
                else reg_2 |= 0200;
        }
}

void ebranch(register int opc,expr_t exp)
{
        /*      Conditional branching; Full displacements are available
                on the 80386, so the welknown trick with the reverse branch
                over a jump is not needed here.
                The only complication here is with the address size, which
                can be set with a prefix. In this case, the user gets what
                he asked for.
        */
        register int sm;
        register long dist;
        int saving = address_long ? 4 : 2;

        if (opc == 0353) saving--;
        dist = exp.val - (DOTVAL + 2);
        if (pass == PASS_2 && dist > 0 && !(exp.typ & S_DOT))
                dist -= DOTGAIN;
        sm = dist > 0 ? fitb(dist-saving) : fitb(dist);
        if ((exp.typ & ~S_DOT) != DOTTYP)
                sm = 0;
        if ((sm = small(sm,saving)) == 0) {
                if (opc == 0353) {
                        emit1(0xe9);
                }
                else {
                        emit1(0xF);
                        emit1(opc | 0x80);
                }
                dist -= saving;
                exp.val = dist;
                adsize_exp(exp, RELPC);
        }
        else {
                if (opc == 0353) {
                        emit1(opc);
                }
                else {
                        emit1(opc | 0x70);
                }
                emit1((int)dist);
        }
}

void branch(register int opc,expr_t exp)
{
        /*      LOOP, JCXZ, etc. branch instructions.
                Here, the offset just must fit in a byte.
        */
        register long dist;

        dist = exp.val - (DOTVAL + 2);
        if (pass == PASS_2 && dist > 0 && !(exp.typ & S_DOT))
                dist -= DOTGAIN;
        fit((exp.typ & ~S_DOT) == DOTTYP && fitb(dist));
        emit1(opc);
        emit1((int)dist);
}

void pushop(register int opc)
{

        regsize(1);
        if (is_segreg(reg_1)) {
                /* segment register */
                if ((reg_1 & 07) <= 3)
                        emit1(6 | opc | (reg_1&7)<<3);
                else {
                        emit1(0xF);
                        emit1(0200 | opc | ((reg_1&7)<<3));
                }
        } else if (is_reg(reg_1)) {
                /* normal register */
                emit1(0120 | opc<<3 | (reg_1&7));
        } else if (opc == 0) {
                if (is_expr(reg_1)) {
                        if (small(exp_1.typ == S_ABS && fitb(exp_1.val),
                                  operand_long ? 3 : 1)) {
                                emit1(0152);
                                emit1((int)(exp_1.val));
                        }
                        else {
                                emit1(0150);
                                RELOMOVE(relonami, rel_1);
                                opsize_exp(exp_1, 1);
                        }
                }
                else {
                        emit1(0377); ea_1(6<<3);
                }
        } else {
                emit1(0217); ea_1(0<<3);
        }
}

void opsize_exp(expr_t exp, int nobyte)
{
        if (! nobyte) {
#ifdef RELOCATION
                newrelo(exp.typ, RELO1);
#endif
                emit1((int)(exp.val));
        }
        else if (operand_long) {
#ifdef RELOCATION
                newrelo(exp.typ, RELO4);
#endif
                emit4((long)(exp.val));
        }
        else {
#ifdef RELOCATION
                newrelo(exp.typ, RELO2);
#endif
                emit2((int)(exp.val));
        }
}

void adsize_exp(expr_t exp, int relpc)
{
        if (address_long) {
#ifdef RELOCATION
                newrelo(exp.typ, RELO4 | relpc);
#endif
                emit4((long)(exp.val));
        }
        else {
#ifdef RELOCATION
                newrelo(exp.typ, RELO2 | relpc);
#endif
                emit2((int)(exp.val));
        }
}

void addop(register int opc)
{

        regsize(opc);
        if (is_reg(reg_2)) {
                /*      Add register to register or memory */
                emit1(opc); ea_1((reg_2&7)<<3);
        } else if (is_acc(reg_1) && is_expr(reg_2)) {
                /*      Add immediate to accumulator */
                emit1(opc | 4);
                RELOMOVE(relonami, rel_2);
                opsize_exp(exp_2, (opc&1));
        } else if (is_expr(reg_2)) {
                /*      Add immediate to register or memory */
                if ((opc&1) == 0) {
                        emit1(0200);
                } else if (! small(exp_2.typ == S_ABS && fitb(exp_2.val),
                                   operand_long ? 3 : 1)) {
                        emit1(0201);
                } else {
                        emit1(0203); opc &= ~1;
                }
                ea_1(opc & 070);
                RELOMOVE(relonami, rel_2);
                opsize_exp(exp_2, (opc&1));
        } else if (is_reg(reg_1)) {
                /*      Add register or memory to register */
                emit1(opc | 2);
                ea_2((reg_1&7)<<3);
        } else
                badsyntax();
}

void rolop(register int opc)
{
        register int oreg;

        oreg = reg_2;
        reg_2 = reg_1;
        regsize(opc);
        if (oreg == (IS_R8 | 1 | (address_long ? 0 : 0300))) {
                /* cl register */
                emit1(0322 | (opc&1)); ea_1(opc&070);
        } else if (is_expr(oreg)) {
            if (small(exp_2.typ == S_ABS && exp_2.val == 1, 1)) {
                /* shift by 1 */
                emit1(0320 | (opc&1)); ea_1(opc&070);
            } else {
                /* shift by byte count */
                emit1(0300 | (opc & 1)); 
                ea_1(opc & 070);
#ifdef RELOCATION
                RELOMOVE(relonami, rel_2);
                newrelo(exp_2.typ, RELO1);
#endif
                emit1((int)(exp_2.val));
            }
        }
        else
                badsyntax();
}

void incop(register int opc)
{

        regsize(opc);
        if ((opc&1) && is_reg(reg_1)) {
                /* word register */
                emit1(0100 | (opc&010) | (reg_1&7));
        } else {
                emit1(0376 | (opc&1));
                ea_1(opc & 010);
        }
}

void callop(register int opc)
{

        regsize(1);
        if (is_expr(reg_1)) {
                if (opc == (040+(0351<<8))) {
                        RELOMOVE(relonami, rel_1);
                        ebranch(0353,exp_1);
                } else {
                        exp_1.val -= (DOTVAL+3 + (address_long ? 2 : 0));
                        emit1(opc>>8);
                        RELOMOVE(relonami, rel_1);
                        adsize_exp(exp_1, RELPC);
                }
        } else {
                emit1(0377); ea_1(opc&070);
        }
}

void xchg(register int opc)
{

        regsize(opc);
        if (! is_reg(reg_1) || is_acc(reg_2)) {
                reverse();
        }
        if (opc == 1 && is_acc(reg_1) && is_reg(reg_2)) {
                emit1(0220 | (reg_2&7));
        } else if (is_reg(reg_1)) {
                emit1(0206 | opc); ea_2((reg_1&7)<<3);
        } else
                badsyntax();
}

void test(register int opc)
{

        regsize(opc);
        if (is_reg(reg_2) || is_expr(reg_1))
                reverse();
        if (is_expr(reg_2)) {
                if (is_acc(reg_1)) {
                        emit1(0250 | opc);
                        RELOMOVE(relonami, rel_2);
                        opsize_exp(exp_2, (opc&1));
                }
                else {
                        emit1(0366 | opc);
                        ea_1(0<<3);
                        RELOMOVE(relonami, rel_2);
                        opsize_exp(exp_2, (opc&1));
                }
        } else if (is_reg(reg_1)) {
                emit1(0204 | opc); ea_2((reg_1&7)<<3);
        } else
                badsyntax();
}

void mov(register int opc)
{

        regsize(opc);
        if (is_segreg(reg_1)) {
                /* to segment register */
                emit1(0216); ea_2((reg_1&07)<<3);
        } else if (is_segreg(reg_2)) {
                /* from segment register */
                emit1(0214); ea_1((reg_2&07)<<3);
        } else if (is_expr(reg_2)) {
                /* from immediate */
                if (is_reg(reg_1)) {
                        /* to register */
                        emit1(0260 | opc<<3 | (reg_1&7)); 
                } else {
                        /* to memory */
                        emit1(0306 | opc); ea_1(0<<3); 
                }
                RELOMOVE(relonami, rel_2);
                opsize_exp(exp_2, (opc&1));
        } else if (rm_1 == 05 && is_acc(reg_2)) {
                /* from accumulator to memory  (displacement) */
                emit1(0242 | opc);
                RELOMOVE(relonami, rel_1);
                adsize_exp(exp_1, 0);
        } else if (rm_2 == 05 && is_acc(reg_1)) {
                /* from memory (displacement) to accumulator */
                emit1(0240 | opc);
                RELOMOVE(relonami, rel_2);
                adsize_exp(exp_2, 0);
        } else if (is_reg(reg_2)) {
                /* from register to memory or register */
                emit1(0210 | opc); ea_1((reg_2&07)<<3);
        } else if (is_reg(reg_1)) {
                /* from memory or register to register */
                emit1(0212 | opc); ea_2((reg_1&07)<<3);
        } else
                badsyntax();
}

void extshft(int opc, int reg)
{
        int oreg2 = reg_2;

        reg_2 = reg_1;
        regsize(1);

        emit1(0xF);
        if (oreg2 == (IS_R8 | 1 | (address_long ? 0 : 0300))) {
                /* cl register */
                emit1(opc|1);
                ea_1(reg << 3);
        }
        else if (is_expr(oreg2)) {
                emit1(opc);
                ea_1(reg << 3);
#ifdef RELOCATION
                RELOMOVE(relonami, rel_2);
                newrelo(exp_2.typ, RELO1);
#endif
                emit1((int)(exp_2.val));
        }
        else    badsyntax();
}

void bittestop(int opc)
{
        regsize(1);
        emit1(0xF);
        if (is_expr(reg_2)) {
                emit1(0272);
                ea_1(opc << 3);
#ifdef RELOCATION
                RELOMOVE(relonami, rel_2);
                newrelo(exp_2.typ, RELO1);
#endif
                emit1((int)(exp_2.val));
        }
        else if (is_reg(reg_2)) {
                emit1(0203 | (opc<<3));
                ea_1((reg_2&7)<<3);
        }
        else    badsyntax();
}

void imul(int reg)
{
        /*      This instruction is more elaborate on the 80386. Its most
                general form is:
                        imul reg, reg_or_mem, immediate.
                This is the form processed here.
        */
        regsize(1);
        if (is_expr(reg_1)) {
                /* To also handle
                        imul reg, immediate, reg_or_mem
                */
                reverse();
        }
        if (is_expr(reg_2)) {
                /* The immediate form; two cases: */
                if (small(exp_2.typ == S_ABS && fitb(exp_2.val),
                          operand_long ? 3 : 1)) {
                        /* case 1: 1 byte encoding of immediate */
                        emit1(0153);
                        ea_1((reg & 07) << 3);
                        emit1((int)(exp_2.val));
                }
                else {
                        /* case 2: WORD or DWORD encoding of immediate */
                        emit1(0151);
                        ea_1((reg & 07) << 3);
                        RELOMOVE(relonami, rel_2);
                        opsize_exp(exp_2, 1);
                }
        }
        else if (is_reg(reg_1) && ((reg_1&7) == (reg & 07))) {
                /* the "reg" field and the "reg_or_mem" field are the same,
                   and the 3rd operand is not an immediate ...
                */
                if (reg == 0) {
                        /* how lucky we are, the target is the ax register */
                        /* However, we cannot make an optimization for f.i.
                                imul eax, blablabla
                           because the latter does not affect edx, whereas
                                imul blablabla
                           does! Therefore, "reg" is or-ed with 0x10 in the
                           former case, so that the test above fails.
                        */
                        emit1(0367);
                        ea_2(050);
                }
                else {
                        /* another register ... */
                        emit1(0xF);
                        emit1(0257);
                        ea_2((reg & 07) << 3);
                }
        }
        else badsyntax();
}

// vim: ts=8 sw=8 et

