/* $Id$ */
/*
 * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
 * See the copyright notice in the ACK home directory, in the file "Copyright".
 */
/* @(#)mach5.c	1.16 */
/*
 * Motorola 68000/68010 auxiliary functions
 */

static int extension_offset(void)
{
	switch(curr_instr) {
	case MOVEM:
	case FMOVE:
	case FMOVEM:
	case FDYADIC:
	case FMONADIC:
	case FSINCOS:
	case FSCC:
	case FTST:
		return 4;
	}
	return 2;
}

void ea_1(int sz, int bits)
{
	register int flag;

	if (mrg_1 > 074)
		serror("no specials");
	if ((flag = eamode[mrg_1>>3]) == 0)
		if ((flag = eamode[010 + (mrg_1&07)]) == 0)
			flag = eamode[015 + (sz>>6)];
	if ((mrg_1 & 070) == 010)
		checksize(sz, 2|4);
	bits &= ~flag;
	if (bits)
		serror("bad addressing category");
	if (flag & FITW)
		Xfit (fitw(exp_1.val) ||
		      (mrg_1 == 074 && fit16(exp_1.val))
		) ;
	if (flag & FITB) {
		Xfit (fitb(exp_1.val) ||
		      (mrg_1 == 074 && fit8(exp_1.val))
		);
		if (mrg_1 == 074)
			exp_1.val &= 0xFF;
	}
#ifdef RELOCATION
	RELOMOVE(relonami, rel_1);
	if (flag & ~0xFF)
		newrelo(exp_1.typ, (flag>>8) | RELBR | RELWR);
#endif
	if (flag & PUTL)
		emit4(exp_1.val);
	if (flag & PUTW)
		emit2(loww(exp_1.val));
}

void ea_2(int sz, int bits)
{ 
	mrg_1 = mrg_2;
	exp_1 = exp_2;
	RELOMOVE(rel_1, rel_2);
	ea_1(sz, bits);
}

void indexmode(int hibyte)
{
	Xfit(fitb(exp_2.val));
	exp_2.val = hibyte | lowb(exp_2.val);
}

void checksize(int sz, int bits)
{
	if ((bits & (1 << (sz>>6))) == 0)
		serror("bad size");
}

void test68010(void)
{
	if (model != 1 && pass == PASS_3)
		warning("68010 instruction");
}

void badoperand(void)
{
	serror("bad operands");
}

void shift_op(int opc, int sz)
{
	if (mrg_1 < 010 && mrg_2 < 010) {
		emit2((opc&0170470) | sz | mrg_1<<9 | mrg_2);
		return;
	}
	if (exp_1.typ != S_ABS || mrg_1 != 074) {
		badoperand();
		return;
	}
	if (mrg_2 < 010) {
		Xfit(fit3(exp_1.val));
		emit2((opc&0170430) | sz | low3(exp_1.val)<<9 | mrg_2);
		return;
	}
	checksize(sz, 2);
	Xfit(exp_1.val == 1);
	emit2((opc&0177700) | mrg_2);
	ea_2(SIZE_W, MEM|ALT);
}

void bitop(int opc)
{
	register int bits;

	bits = DTA|ALT;
	if (opc == 0 && (mrg_1 < 010 || mrg_2 != 074))
		bits = DTA;
	if (mrg_1 < 010) {
		emit2(opc | 0400 | mrg_1<<9 | mrg_2);
		ea_2(0, bits);
		return;
	}
	if (mrg_1 == 074) {
		emit2(opc | 04000 | mrg_2);
		ea_1(SIZE_W, 0);
		ea_2(0, bits);
		return;
	}
	badoperand();
}

void add(int opc, int sz)
{
	if ((mrg_2 & 070) == 010)
		checksize(sz, 2|4);
	if (
		mrg_1 == 074
		&&
		small(
			exp_1.typ==S_ABS && fit3(exp_1.val),
			sz==SIZE_L ? 4 : 2
		)
	) {
		emit2((opc&0400) | 050000 | low3(exp_1.val)<<9 | sz | mrg_2);
		ea_2(sz, ALT);
		return;
	}
	if (mrg_1 == 074 && (mrg_2 & 070) != 010) {
		emit2((opc&03000) | sz | mrg_2);
		ea_1(sz, 0);
		ea_2(sz, DTA|ALT);
		return;
	}
	if ((mrg_2 & 070) == 010) {
		emit2((opc&0170300) | (mrg_2&7)<<9 | sz<<1 | mrg_1);
		ea_1(sz, 0);
		return;
	}
	if (to_dreg(opc, sz, 0))
		return;
	if (from_dreg(opc, sz, ALT|MEM))
		return;
	badoperand();
}

void and(int opc, int sz)
{
	if (mrg_1 == 074 && mrg_2 >= 076) {	/* ccr or sr */
		if (sz != SIZE_NON)
			checksize(sz, mrg_2==076 ? 1 : 2);
		else
			sz = (mrg_2==076 ? SIZE_B : SIZE_W);
		emit2((opc&07400) | sz | 074);
		ea_1(sz, 0);
		return;
	}
	if (sz == SIZE_NON)
		sz = SIZE_DEF;
	if (mrg_1 == 074) {
		emit2((opc&07400) | sz | mrg_2);
		ea_1(sz, 0);
		ea_2(sz, DTA|ALT);
		return;
	}
	if ((opc & 010000) == 0 && to_dreg(opc, sz, DTA))
		return;
	if (from_dreg(opc, sz, (opc & 010000) ? DTA|ALT : ALT|MEM))
		return;
	badoperand();
}

int to_dreg(int opc, int sz, int bits)
{
	if ((mrg_2 & 070) != 000)
		return(0);
	emit2((opc & 0170000) | sz | (mrg_2&7)<<9 | mrg_1);
	ea_1(sz, bits);
	return(1);
}

int from_dreg(int opc, int sz, int bits)
{
	if ((mrg_1 & 070) != 000)
		return(0);
	emit2((opc & 0170000) | sz | (mrg_1&7)<<9 | 0400 | mrg_2);
	ea_2(sz, bits);
	return(1);
}

void cmp(int sz)
{
	register int opc;

	if ((mrg_1&070) == 030 && (mrg_2&070) == 030) {
		emit2(0130410 | sz | (mrg_1&7) | (mrg_2&7)<<9);
		return;
	}
	if (mrg_1 == 074 && (mrg_2 & 070) != 010) {
		if (mrg_2 == 072) {
			/* In this case, the ea707172 routine changed the
			   addressing mode to PC-relative. However, this is
			   not allowed for this instruction. Change it back
			   to absolute, but also correct for the optimization
			   that the ea707172 routine thought it made.
			*/
			if (pass == PASS_1) {
				/* In this case, the user wrote it PC-relative.
				   Error.
				*/
				badoperand();
			}
			mrg_2 = 071;
			exp_2.val += DOTVAL+2;
			if (pass == PASS_2) {
				DOTGAIN -= 2;
			}
		}
		emit2(06000 | sz | mrg_2);
		ea_1(sz, 0);
		ea_2(sz, DTA|ALT);
		return;
	}
	if (mrg_2 < 020) {
		if (mrg_2 >= 010) {
			checksize(sz, 2|4);
			opc = 0130300 | sz<<1;
			mrg_2 &= 7;
		} else
			opc = 0130000 | sz;
		emit2(opc | mrg_2<<9 | mrg_1);
		ea_1(sz, 0);
		return;
	}
	badoperand();
}

void move(int sz)
{
	register int opc;

	if (mrg_1 > 074 || mrg_2 > 074) {
		move_special(sz);
		return;
	}
	if (sz == SIZE_NON)
		sz = SIZE_DEF;
	if (
		mrg_2<010
		&&
		mrg_1==074
		&&
		sz==SIZE_L
		&&
		small(exp_1.typ==S_ABS && fitb(exp_1.val), 4)
	) {
		emit2(070000 | mrg_2<<9 | lowb(exp_1.val));
		return;
	}
	switch (sz) {
	case SIZE_B:	opc = 010000; break;
	case SIZE_W:	opc = 030000; break;
	case SIZE_L:	opc = 020000; break;
	}
	emit2(opc | mrg_1 | (mrg_2&7)<<9 | (mrg_2&070)<<3);
	ea_1(sz, 0);
	ea_2(sz, ALT);
}

void move_special(int sz)
{
	if (mrg_2 >= 076) {
		if (sz != SIZE_NON)
			checksize(sz, 2);
		emit2(042300 | (mrg_2==076?0:01000) | mrg_1);
		ea_1(SIZE_W, DTA);
		return;
	}
	if (mrg_1 >= 076) {
		if (mrg_1 == 076)
			test68010();
		if (sz != SIZE_NON)
			checksize(sz, 2);
		emit2(040300 | (mrg_1==076?01000:0) | mrg_2);
		ea_2(SIZE_W, DTA|ALT);
		return;
	}
	if (sz != SIZE_NON)
		checksize(sz, 4);
	if (mrg_1==075 && (mrg_2&070)==010) {
		emit2(047150 | (mrg_2&7));
		return;
	}
	if (mrg_2==075 && (mrg_1&070)==010) {
		emit2(047140 | (mrg_1&7));
		return;
	}
	badoperand();
}

int reverse(register int regs, int max)
{
	register int r, i;

	r = regs; regs = 0;
	for (i = max; i > 0; i--) {
		regs <<= 1;
		if (r & 1) regs++;
		r >>= 1;
	}
	return regs;
}

void movem(int dr, int sz, int regs)
{
	register int i;

	if ((mrg_2>>3) == 04) {
		regs = reverse(regs, 16);
	}
	checksize(sz, 2|4);
	if ((mrg_2>>3)-3 == dr)
		badoperand();
	emit2(044200 | dr<<10 | (sz & 0200) >> 1 | mrg_2);
	emit2(regs);
	i = CTR;
	if (dr == 0 && (mrg_2&070) == 040)
		i = MEM;
	if (dr != 0 && (mrg_2&070) == 030)
		i = MEM;
	if (dr == 0)
		i |= ALT;
	ea_2(sz, i);
}

void movep(int sz)
{
	checksize(sz, 2|4);
	if (mrg_1<010 && (mrg_2&070)==050) {
		emit2(0610 | (sz & 0200)>>1 | mrg_1<<9 | (mrg_2&7));
		ea_2(sz, 0);
		return;
	}
	if (mrg_2<010 && (mrg_1&070)==050) {
		emit2(0410 | (sz & 0200)>>1 | mrg_2<<9 | (mrg_1&7));
		ea_1(sz, 0);
		return;
	}
	badoperand();
}

void branch(int opc, expr_t exp)
{
	register int sm;

	exp.val -= (DOTVAL + 2);
	if ((pass == PASS_2) 
	    &&
	    (exp.val > 0)
	    &&
	    ((exp.typ & S_DOT) == 0)
	   )
		exp.val -= DOTGAIN;
	sm = fitb(exp.val);
	if ((exp.typ & ~S_DOT) != DOTTYP)
		sm = 0;
	if (small(sm,2)) {
		if (exp.val == 0)
			emit2(047161);	/* NOP */
		else
			emit2(opc | lowb(exp.val));
	} else {
		Xfit(fitw(exp.val));
		emit2(opc);
#ifdef RELOCATION
		newrelo(exp.typ, RELPC|RELO2|RELBR|RELWR);
#endif
		emit2(loww(exp.val));
	}
}

void ea5x73(int rg, int sz)
{
	if ((exp_2.typ & ~S_DOT) == DOTTYP) {
		/* pc relative with index */
		if (sz == SIZE_NON)
			sz = SIZE_DEF;
		exp_2.val -= (DOTVAL + extension_offset());
		mrg_2 = 073;
		checksize(sz, 2|4);
		indexmode(rg<<12 | (sz&0200)<<4);
		return;
	}
	/* displacement */
	mrg_2 = 050 | rg;
	if (pass == PASS_1)	/* tricky, maybe 073 in next passes */
		return;
	if ((rg & 010) == 0 || sz != SIZE_NON)
		badoperand();
}

void ea707172(int sz)
{
	register int sm;

	mrg_2 = 071;
	switch (sz) {
	case SIZE_B:
		badoperand();
	case SIZE_W:
		mrg_2 = 070;
	case SIZE_L:
		return;
	case SIZE_NON:
		break;
	}
	if (pass == PASS_1) {
		/*
		 * reserve a bit in the bittable
		 * in the following passes one call to small()
		 * will be done, but we don't know which one
		 * since exp_2.typ cannot be trusted
		 */
		small(0, 2);
		return;
	}
	if ((exp_2.typ & ~S_DOT) == DOTTYP) {
		int off = extension_offset();
		sm = fitw(exp_2.val-(DOTVAL+off));
		sm = small(sm, 2);
		if (sm) {	/* pc relative */
			exp_2.val -= (DOTVAL+off);
			mrg_2 = 072;
		}
	} else {
		sm = fitw(exp_2.val);
#ifdef ASLD
		if (exp_2.typ & S_VAR)
			sm = 0;
#else
		if (exp_2.typ != S_ABS)
			sm = 0;
#endif /* ASLD */
		sm = small(sm, 2);
		if (sm)
			mrg_2 = 070;
	}
}

void ea6x(int rg, int ir, int sz)
{
	mrg_2 = 060 | rg;
	checksize(sz, 2|4);
	indexmode(ir<<12 | (sz&0200)<<4);
}

void ea72(void)
{
	mrg_2 = 072;
	exp_2.val -= (DOTVAL + extension_offset());
}

void ea73(int ri, int sz)
{
	mrg_2 = 073;
	exp_2.val -= (DOTVAL + extension_offset());
	checksize(sz, 2|4);
	indexmode(ri<<12 | (sz&0200)<<4);
}

void Xnofit(void)
{
	if (pass == PASS_3) serror("too big");
}

void fbranch(int opc, expr_t exp)
{
	register int sm;

	exp.val -= (DOTVAL + 2);
	if ((pass == PASS_2) 
	    &&
	    (exp.val > 0)
	    &&
	    ((exp.typ & S_DOT) == 0)
	   )
		exp.val -= DOTGAIN;
	sm = fitw(exp.val);
	if ((exp.typ & ~S_DOT) != DOTTYP)
		sm = 0;
	if (small(sm,2)) {
		emit2(0170200|co_id|opc);
		emit2(loww(exp.val));
		return;
	}
	emit2(0170300|co_id|opc); /* 4 byte offset */
#ifdef RELOCATION
	newrelo(exp.typ, RELPC|RELO4|RELBR|RELWR);
#endif
	emit4(exp.val);
}

void ch_sz_dreg(int size, int mode)
{
	if (mode == 0 &&
	    (size == FSIZE_X || size == FSIZE_P || size == FSIZE_D))
		serror("illegal size for data register");
}

void check_fsize(int sz, int size)
{
	if (sz != size) serror("bad size");
}
