/*
 *    (c)Copyright 1992-1997 Obvious Implementations Corp.  Redistribution and
 *    use is allowed under the terms of the DICE-LICENSE FILE,
 *    DICE-LICENSE.TXT.
 */

/*
 *  ASM2.C
 *
 */

/*
**      $Filename: asm2.c $
**      $Author: dice $
**      $Revision: 30.326 $
**      $Date: 1995/12/24 06:09:35 $
**      $Log: asm2.c,v $
 * Revision 30.326  1995/12/24  06:09:35  dice
 * .
 *
 * Revision 30.5  1994/06/13  18:37:22  dice
 * .
 *
 * Revision 30.0  1994/06/10  18:04:46  dice
 * .
 *
 * Revision 1.6  1993/11/22  00:28:36  jtoebes
 * Final cleanup to eliminate all cerror() messages with strings.
 *
 * Revision 1.5  1993/09/11  22:35:28  jtoebes
 * Fixed BUG06009.
 * asm_cmp optimized a compare into a tst instruction for unsigned values.
 * Eliminated this improper optimization.
 *
 * Revision 1.4  1993/09/06  09:31:42  jtoebes
 * Fixed BUG06066 - Enforcer hit with float code.
 * CallAsmSupport did not check for the register being null.
 *
**/

#include "defs.h"
#include "asm.h"

#define Max(a,b)    (((a) > (b)) ? (a) : (b))

Prototype void asm_move(Exp *, Stor *, Stor *);
Prototype void asm_move_cast(Exp *, Stor *, Stor *);
Prototype long ReverseOrder(short);
Prototype void asm_cmp(Exp *, Stor *, Stor *, short *);
Prototype long asm_div(Exp *, Stor *, Stor *, Stor *, short);
Prototype long asm_mul(Exp *, Stor *, Stor *, Stor *);
Prototype long asm_mul_requires_call(long);
Prototype void asm_shift(Exp *, long, Stor *, Stor *, Stor *);
Prototype void CallAsmSupport(Exp *, char *, Stor *, Stor *, Stor *, short);
Prototype void asm_bfext(Exp *, Stor *, Stor *);
Prototype void asm_bfsto(Exp *, Stor *, Stor *);
Prototype void asm_bftst(Exp *, Stor *);
Prototype void asm_blockfill(Exp *, Stor *, long, char);
Prototype void asm_illegal(void);
Prototype void asm_moveqAndSwap(long, char, short);

Local void asm_blockop(Exp *, char *, short, Stor *, Stor *);
Local void OptimizeBitField(Stor *, Stor *);

/*
 *  asm_blockop()   is used for block moves and block compares.  When
 *		    used for block compares the loop breaks out when
 *		    a not-equal condition occurs.  The 'cond' passed
 *		    to the routine only effects the final branch, NOT
 *		    the loop-breakout condition.
 */

Local void
asm_blockop(Exp *exp, char *op, short cond, Stor *s, Stor *d)
{
    long n = s->st_Size >> 2;
    long i;
    long o1 = s->st_Offset;
    long o2 = d->st_Offset;
    long label;
    long breaklabel;
    Stor scnt, mcnt, asrc, adst;
    char *dbstr;
    short doRemainder = 1;

    /*
    printf("block %s %s (%d %d) (%d %d)\n",
	StorToString(s, NULL), StorToString(d, NULL), s->st_Size, d->st_Size,
	s->st_Type, d->st_Type
    );
    */

    breaklabel = AllocLabel();

    switch(cond) {
    case COND_EQ:
    case COND_NEQ:
	dbstr = "dbne";     /*  loop until not equal or done (eq)   */
	break;
    default:
	dbstr = "dbf";
	break;
    }


    if (s->st_Type < ST_RelReg || s->st_Type > ST_RegIndex)
	yerror(exp->ex_LexIdx, EERROR_BLOCK_OPERATION_SOURCE);
    if (d->st_Type < ST_RelReg || d->st_Type > ST_RegIndex)
	yerror(exp->ex_LexIdx, EERROR_BLOCK_OPERATION_DEST);

    /*
     *	In the default case we load a data register with the lw count
     *	and two address registers with the base addresses.
     */

    switch(n) {
    default:
	--n;
	LockStorage(s);
	LockStorage(d);
	if (n > 65534) {
	    AllocDataRegister(&mcnt, 2);
	    AllocDataRegister(&scnt, 4);
	} else {
	    AllocDataRegister(&scnt, 2);
	}

	/*
	 *  If long aligned we can reuse s and d registers, but if not
	 *  we need them!
	 */

	if (s->st_Size & 3) {
	    AllocAddrRegister(&asrc);
	    AllocAddrRegister(&adst);
	} else {
	    UnlockStorage(s);
	    AllocAddrRegister(&asrc);
	    UnlockStorage(d);
	    LockStorage(s);
	    AllocAddrRegister(&adst);
	    LockStorage(d);
	}
	LockStorage(&asrc);
	LockStorage(&adst);
	asm_movei(exp, n, &scnt);
	if (n > 65534) {
	    printf("\tmove.l\tD%d,D%d\n", scnt.st_RegNo, mcnt.st_RegNo);
	    printf("\tswap\tD%d\n", mcnt.st_RegNo);
	}
	asm_lea(exp, s, 0, &asrc);
	asm_lea(exp, d, 0, &adst);
	UnlockStorage(s);
	UnlockStorage(d);
	asrc.st_Type = ST_RelReg;
	asrc.st_Offset = 0;
	adst.st_Type = ST_RelReg;
	adst.st_Offset = 0;

	label = AllocLabel();
	asm_label(label);
	printf("\t%s.l\t(A%d)+,(A%d)+\n", op, asrc.st_RegNo - RB_ADDR, adst.st_RegNo - RB_ADDR);
	printf("\t%s\tD%d,l%ld\n", dbstr, scnt.st_RegNo, label);
	if (n > 65534) {
	    if (cond)
		asm_condbra(COND_NEQ, breaklabel);
	    printf("\t%s\tD%d,l%ld\n", dbstr, mcnt.st_RegNo, label);
	}

	/*
	 *  Handle tail-end case here.	Can't handle tail end case
	 *  down below more because the structure might be larger then
	 *  128 bytes.
	 */

	if (s->st_Size & 3) {
	    if (cond)
		asm_condbra(COND_NEQ, breaklabel);
	    switch(s->st_Size & 3) {
	    case 3:
	    case 2:
		printf("\t%s.w\t(A%d)+,(A%d)+\n", op, asrc.st_RegNo - RB_ADDR, adst.st_RegNo - RB_ADDR);
		if ((s->st_Size & 1) == 0)
		    break;
		if (cond)
		    asm_condbra(COND_NEQ, breaklabel);
		/* fall through */
	    case 1:
		printf("\t%s.b\t(A%d),(A%d)\n", op, asrc.st_RegNo - RB_ADDR, adst.st_RegNo - RB_ADDR);
		break;
	    }
	    doRemainder = 0;
	}

	UnlockStorage(&asrc);
	UnlockStorage(&adst);
	FreeRegister(&adst);
	FreeRegister(&asrc);
	FreeRegister(&scnt);
	if (n > 65534)
	    FreeRegister(&mcnt);
	break;
    case 4:	/*  note, LGBO_SIZE (machine.h) */
    case 3:
    case 2:
    case 1:
	if (cond)
	    AllocDataRegister(&mcnt, 4);

	for (i = n - 1; i >= 0; --i) {
	    s->st_Offset = o1 + i * 4;
	    d->st_Offset = o2 + i * 4;
	    if (cond) {
		outop("move", 4, d, &mcnt);
		outop(op, 4, s, &mcnt);
	    } else {
		outop(op, 4, s, d);
	    }

	    if (cond && (i || (s->st_Size & 3)))
		asm_condbra(COND_NEQ, breaklabel);
	}
	if (cond)
	    FreeRegister(&mcnt);
	break;
    case 0:
	break;
    }

    /*
     *	do remainder.  s and d are guarenteed to contain source and
     *	destination bases only if (s->st_Size & 3) != 0
     */

    if (doRemainder) {
	s->st_Offset = o1 + (s->st_Size & ~3);
	d->st_Offset = o2 + (d->st_Size & ~3);

	switch(s->st_Size & 3) {
	case 0:
	    break;
	case 3:     /*	 2 + 1	*/
	case 2:
	    if (cond) {
		AllocDataRegister(&mcnt, 4);
		outop("move", 2, d, &mcnt);
		outop(op, 2, s, &mcnt);
		FreeRegister(&mcnt);
	    } else {
		outop(op, 2, s, d);
	    }
	    if ((s->st_Size & 1) == 0)
		break;
	    s->st_Offset += 2;
	    d->st_Offset += 2;
	case 1:
	    if (cond) {
		AllocDataRegister(&mcnt, 4);
		outop("move", 1, d, &mcnt);
		outop(op, 1, s, &mcnt);
		FreeRegister(&mcnt);
	    } else {
		outop(op, 1, s, d);
	    }
	    break;
	default:
	    Assert(0);
	}
    }
    if (cond)
	asm_label(breaklabel);

    s->st_Offset = o1;
    d->st_Offset = o2;
}

#ifdef NOTDEF
/*
 *  fill a block (d is a pointer) with a value, simple stupid
 *
 *  currently d must be register-relative
 */

void
asm_blockfill(exp, d, n, v)
Exp *exp;
Stor *d;
long n;
char v;
{
    Stor t;
    Stor r;
    Stor adst;
    Stor dcnt;
    long label;

    asm_getind(&VoidPtrType, d, &t, -1, -1, 0);

    switch(n) {
    case 0:
	break;
    case 6:
	t.st_Size = 2;
	asm_movei(exp, 0, &t);
	n -= 2;
	t.st_Offset += 2;
    case 1:
    case 2:
    case 4:
	t.st_Size = n;
	asm_movei(exp, 0, &t);
	break;
    case 8:
    case 12:
    case 16:
	AllocDataRegister(&r, 4);
	t.st_Size = 4;
	asm_movei(exp, 0, &r);
	while (n) {
	    asm_move(exp, &r, &t);
	    t.st_Offset += 4;
	    n -= 4;
	}
	FreeRegister(&r);
	break;
    default:	    /*	byte fill   */
	label = AllocLabel();

	AllocDataRegister(&r, 4);
	AllocDataRegister(&dcnt, 2);
	AllocAddrRegister(&adst);
	asm_movei(exp, 0, &r);
	asm_move(exp, d, &adst);
	asm_movei(exp, n-1, &dcnt);
	asm_label(label);
	printf("\tmove.b\tD%d,(A%d)+\n", r.st_RegNo, adst.st_RegNo - RB_ADDR);
	printf("\tdbf\tD%d,l%ld\n", dcnt.st_RegNo, label);
	FreeRegister(&r);
	FreeRegister(&dcnt);
	FreeRegister(&adst);
	break;
    }
}

#endif

/*
 *  asm_move()	strictly moves objects of the same size
 */

void
asm_move(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    /*
    fprintf(stderr, "move %s %s (%d %d) (%d %d)\n",
	StorToString(s, NULL), StorToString(d, NULL), s->st_Size, d->st_Size,
	s->st_Type, d->st_Type
    );
    */

    if (d->st_Flags & SF_LEA)
	yerror(exp->ex_LexIdx, EFATAL_DEST_NOT_LVALUE);

    if (SameStorage(s, d))
	return;
    if (s->st_Type == ST_IntConst) {
	int value = s->st_IntConst;
	int swapit = 0;

	switch (d->st_Size) {
	case 1:     /*	e.g. unsigned 0xF0 -> char == moveq #-10    */
	    value = (char)value;
	    break;
	case 2:     /*	e.g. unsigned 0xFFF0 -> short == moveq #-10 */
	    value = (short)value;
	    break;
	}

	/*
	 *  can we moveq or moveq/swap ?
	 *
	 *  note: bug in lattice C, comparison with 0x is done signed. 0x
	 *  hex constant is supposed to be unsigned.
	 */

	if ((ulong)value >= 0x10000 && (long)value < -128 && (long)value > 127) {
	    swapit = 1;
	    value = ((ulong)value >> 16) | ((ulong)value << 16);
	}
	if (value >= -128 && value < 128) {
	    Stor stor;

	    if (d->st_Type == ST_Reg) {
		if (d->st_RegNo < RB_ADDR) {
		    asm_moveqAndSwap(value, d->st_RegNo + '0', swapit);
#ifdef NOTDEF
		    printf("\tmoveq.l\t#%d,D%c\n", value, d->st_RegNo + '0');
		    if (swapit)
			printf("\tswap\tD%c\n", d->st_RegNo + '0');
#endif
		    return;
		}

		/*
		 *  optimize for address, move #n,An sign extends to
		 *  address register.
		 */

		if (s->st_IntConst == 0) {  /*	value may be invalid due to swap */
		    outop("suba", 4, d, d);
		    return;
		}
		if (s->st_IntConst < 32768) {
		    outop("move", 2, s, d);
		    return;
		}
	    }
	    if (d->st_Size == 4) {
		AllocDataRegister(&stor, 4);
		asm_moveqAndSwap(value, stor.st_RegNo + '0', swapit);
#ifdef NOTDEF
		printf("\tmoveq.l\t#%d,D%c\n", value, stor.st_RegNo + '0');
		if (swapit)
		    printf("\tswap\tD%c\n", stor.st_RegNo + '0');
#endif
		outop("move", 0, &stor, d);
		FreeRegister(&stor);
		return;
	    }
	}

	/*
	 *  note: value may be invalid due to swap
	 */

	outop("move", 0, s, d);
	return;
    }
    if (s->st_Type == ST_FltConst) {
	long fltv[4];
	long offset = d->st_Offset;
	long size   = d->st_Size;
	short i;

        /* Make sure that we are not trying to do a move on something that we   */
        /* can't bump the offset for an indirect move.  In the case of a simple */
        /* 4 byte float, it is legal to have a direct register.  Also, when we  */
        /* Move to 68881 support, we will need to relax this check              */
	if ((d->st_Type < ST_RelReg || d->st_Type > ST_RegIndex) &&
	    ((d->st_Type != ST_Reg) && (size != 4)))
	{
	    dbprintf(("softerror, blockop struct, type d=%d\n", d->st_Type));
	    Assert(0);
	}

	asm_fltconst(exp, s, fltv);

	d->st_Size = 4;
	for (i = 0; i < size; i += 4) {
	    asm_movei(exp, fltv[i>>2], d);
	    d->st_Offset += 4;
	}
	d->st_Offset = offset;
	d->st_Size   = size;
	return;
    }
    if (s->st_Size != d->st_Size)
    {
	dbprintf(("asm_move: size mismatch %ld %ld", s->st_Size, d->st_Size));
	Assert(0);
    }

    if (s->st_Flags & SF_LEA) {
	if (d->st_Size != 4)
	{
	    dbprintf(("move ptr, dest size not 4"));
	    Assert(0);
	}
	if (d->st_Type == ST_Reg && d->st_RegNo >= RB_ADDR) {
	    outop("lea", 4, s, d);
	} else if (s->st_Type == ST_RelReg && s->st_Offset == 0) {
	    s->st_Type = ST_Reg;
	    outop("move", 4, s, d);
	    s->st_Type = ST_RelReg;
	} else {
	    Stor t;
	    LockStorage(s);
	    AllocAddrRegister(&t);
	    UnlockStorage(s);
	    outop("lea", 4, s, &t);
	    outop("move", 0, &t, d);
	    FreeRegister(&t);
	}
	return;
    }
    if (s->st_Size < d->st_Size)    /* XXX != */
	eprintf(1, "move: size mismatch %d %d\n", s->st_Size, d->st_Size);

    if (s->st_Size <= 4 && s->st_Size != 3) {
	Stor stor;
	/* We have to check for the case where we are doing a byte move from an */
	/* address register and handle it specially.                            */
	/* To accomplish this, we will need a data register.                    */
	if ((s->st_Size == 1) &&       /* Byte move                             */
	    (s->st_Type == ST_Reg) &&  /* From a register                       */
	    (s->st_RegNo >= RB_ADDR))  /* But it must be an address register    */
	{

	   AllocDataRegister(&stor, 4);
	   s->st_Size = 2;
	   outop("move", 0, s, &stor);
	   s->st_Size = 1;
	   s = &stor;
	}
	outop("move", 0, s, d);
	return;
    }
    asm_blockop(exp, "move", 0, s, d);
}

/*
 *  This is a hack.. this combination occurs so often that using printf()s
 *  effects the speed of compilation!
 */

void
asm_moveqAndSwap(long value, char dregC, short swapit)
{
    char *ptr = StorBuf[0];

    ptr[0] = '\t';
    ptr[1] = 'm';
    ptr[2] = 'o';
    ptr[3] = 'v';
    ptr[4] = 'e';
    ptr[5] = 'q';
    ptr[6] = '.';
    ptr[7] = 'l';
    ptr[8] = '\t';
    ptr[9] = '#';
    ptr += 10;
    if (value) {
	ptr = itodec(ptr, value);
    } else {
	*ptr = '0';
	++ptr;
    }

    ptr[0] = ',';
    ptr[1] = 'D';
    ptr[2] = dregC;
    ptr[3] = '\n';
    ptr += 4;

    if (swapit) {
	ptr[0] = '\t';
	ptr[1] = 's';
	ptr[2] = 'w';
	ptr[3] = 'a';
	ptr[4] = 'p';
	ptr[5] = '\t';
	ptr[6] = 'D';
	ptr[7] = dregC;
	ptr[8] = '\n';
	ptr += 9;
    }
    fwrite(StorBuf[0], ptr - StorBuf[0], 1, stdout);
#ifdef NOTDEF
		printf("\tmoveq.l\t#%d,D%c\n", value, stor.st_RegNo + '0');
		if (swapit)
		    printf("\tswap\tD%c\n", stor.st_RegNo + '0');
#endif
}


/*
 *  asm_move_cast() works like asm_move() but only on integer objects.	Objects
 *  of different sizes may be moved.
 */

void
asm_move_cast(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    if (s->st_Size > d->st_Size) {
	long off = s->st_Offset;
	long size = s->st_Size;

	switch(s->st_Type) {
	case ST_PtrConst:
	case ST_RelReg:
	case ST_RelArg:
	case ST_RelName:
	case ST_RelLabel:
	case ST_RegIndex:
	    s->st_Offset += s->st_Size - d->st_Size;
	    break;
	}
	s->st_Size = d->st_Size;
	asm_move(exp, s, d);
	s->st_Offset = off;
	s->st_Size = size;
	return;
    }
    if (s->st_Size == d->st_Size) {
	asm_move(exp, s, d);
	return;
    }
    if (SameStorage(s, d)) {
	if (s->st_Flags & SF_UNSIGNED) {
	    long val;
	    Stor con;

	    if (s->st_Size == 1) {
		if (d->st_Size == 2)
		    val = 0x00FF;
		else
		    val = 0x000000FF;
	    } else {
		val = 0x0000FFFF;
	    }
	    AllocConstStor(&con, val, &LongType);
	    asm_and(exp, &con, d, d);
	    return;
	} else if (s->st_Type == ST_Reg && s->st_RegNo < RB_ADDR) {
	    if (s->st_Size == 1) {
		if (d->st_Size >= 2)
		    outop("ext", 2, NULL, d);
		if (d->st_Size == 4)
		    outop("ext", 4, NULL, d);
	    } else {
		outop("ext", 4, NULL, d);
	    }
	    return;
	}
    }

    /*
     *	not a reg or not same storage or not a reg and same storage.
     */
    {
	Stor tmp;

	LockStorage(s);
	AllocDataRegister(&tmp, d->st_Size);
	UnlockStorage(s);
	if (s->st_Flags & SF_UNSIGNED) {
	    asm_movei(exp, 0, &tmp);
	    tmp.st_Size = s->st_Size;
	    asm_move(exp, s, &tmp);
	    tmp.st_Size = d->st_Size;
	    asm_move(exp, &tmp, d);
	} else {
	    tmp.st_Size = s->st_Size;
	    asm_move(exp, s, &tmp);
	    if (s->st_Size == 1)
		outop("ext", 2, NULL, &tmp);
	    if (d->st_Size == 4)
		outop("ext", 4, NULL, &tmp);
	    tmp.st_Size = d->st_Size;
	    asm_move(exp, &tmp, d);
	}
	FreeRegister(&tmp);
    }
}

/*
 *  Reversing the opcode ordering in the compare is not the same as
 *  cond = -cond
 */

long
ReverseOrder(short cond)
{
    switch(cond) {
    case COND_LT:
	return(COND_GT);
    case COND_LTEQ:
	return(COND_GTEQ);
    case COND_GT:
	return(COND_LT);
    case COND_GTEQ:
	return(COND_LTEQ);
    case COND_EQ:
	return(COND_EQ);
    case COND_NEQ:
	return(COND_NEQ);
    case CF_UNS|COND_LT:
	return(CF_UNS|COND_GT);
    case CF_UNS|COND_LTEQ:
	return(CF_UNS|COND_GTEQ);
    case CF_UNS|COND_GT:
	return(CF_UNS|COND_LT);
    case CF_UNS|COND_GTEQ:
	return(CF_UNS|COND_LTEQ);
    case CF_UNS|COND_EQ:
	return(CF_UNS|COND_EQ);
    case CF_UNS|COND_NEQ:
	return(CF_UNS|COND_NEQ);

    case -COND_LT:	    /*	gteq	*/
	return(COND_LTEQ);
    case -COND_LTEQ:	    /*	gt	*/
	return(COND_LT);
    case -COND_GT:	    /*	lteq	*/
	return(COND_GTEQ);
    case -COND_GTEQ:	    /*	lt	*/
	return(COND_GT);
    case -COND_EQ:	    /*	neq	*/
	return(COND_NEQ);
    case -COND_NEQ:	    /*	eq	*/
	return(COND_EQ);

    case -(CF_UNS|COND_LT):	/*  gteq    */
	return(CF_UNS|COND_LTEQ);
    case -(CF_UNS|COND_LTEQ):	/*  gt	    */
	return(CF_UNS|COND_LT);
    case -(CF_UNS|COND_GT):	/*  lteq    */
	return(CF_UNS|COND_GTEQ);
    case -(CF_UNS|COND_GTEQ):	/*  lt	    */
	return(CF_UNS|COND_GT);
    case -(CF_UNS|COND_EQ):	/*  neq     */
	return(COND_NEQ);
    case -(CF_UNS|COND_NEQ):	/*  eq	    */
	return(COND_EQ);
    }
    dbprintf(("Unknown cond %d\n", cond));
    Assert(0);
    return(0);			/* not reached */
}

void
asm_cmp(exp, s1, s2, pcond)
Exp *exp;
Stor *s1;
Stor *s2;
short *pcond;
{
    Stor stor1;
    Stor stor2;

    if ((s1->st_Flags | s2->st_Flags) & SF_BITFIELD)
    {
	dbprintf(("compare - bitfield"));
	Assert(0);
    }

    Assert(s1->st_Size == s2->st_Size);
    if (s2->st_Type == ST_Reg) {
	SWAPS(s1, s2);
	*pcond = ReverseOrder(*pcond);
    }
    if (ImmStorage(s1)) {
	SWAPS(s1, s2);
	*pcond = ReverseOrder(*pcond);
    }
    if (!(s1->st_Flags & SF_LEA) && s2->st_Type == ST_IntConst) {
	/*
	 *  if cmp.l #long and long is in moveq range then use
	 *  moveq/cmp instead.	But, if the constant is 0 then use
	 *  a tst instead.  Since we know the branch condition we could
	 *  also conceivably optimize compares with the constants 1 and
	 *  -1 as well (a >= 1 === a > 0).
	 */
	if (s2->st_IntConst == 0 &&
	    (s1->st_Type != ST_Reg || s1->st_RegNo < RB_ADDR) &&
	    ((*pcond & CF_UNS) == 0)) {
	    outop("tst", 0, NULL, s1);
	    return;
	}
	if (s1->st_Size == 4 && s2->st_IntConst >= -128 && s2->st_IntConst < 128) {
	    LockStorage(s1);
	    AllocDataRegister(&stor1, 4);
	    UnlockStorage(s1);
	    asm_move(exp, s2, &stor1);
	    outop("cmp", 0, s1, &stor1);
	    *pcond = ReverseOrder(*pcond);
	    FreeRegister(&stor1);
	    return;
	}
	outop("cmp", 0, s2, s1);
	return;


#ifdef NOTDEF
	if (s2->st_IntConst < -128 || s2->st_IntConst > 127 || s2->st_Size < 4) {
	    if (s1->st_Flags & SF_LEA) {
		LockStorage(s2);
		AllocAddrRegister(&stor1);
		asm_move(exp, s1, &stor1);
		UnlockStorage(s2);
		outop("cmp", 0, &stor1, s2);
		FreeRegister(&stor1);		       xXX
		*pcond = ReverseOrder(*pcond);	    XXX
		return;
	    }
	    outop("cmp", 0, s2, s1);
	    return;
	}
#endif
    }

    if (s2->st_Flags & SF_LEA) {	/*  cmp ea,An	*/
	SWAPS(s1, s2);
	*pcond = ReverseOrder(*pcond);
    }

    if ((s1->st_Flags | s2->st_Flags) & SF_LEA) {
	if (s1->st_Flags & SF_LEA) {
	    LockStorage(s2);
	    AllocAddrRegister(&stor1);
	    asm_move(exp, s1, &stor1);
	    UnlockStorage(s2);
	} else {
	    stor1 = *s1;
	}
	if (s2->st_Flags & SF_LEA) {
	    LockStorage(s1);
	    AllocAddrRegister(&stor2);
	    asm_move(exp, s2, &stor2);
	    UnlockStorage(s1);
	} else {
	    stor2 = *s2;
	}
	outop("cmp", 0, &stor2, &stor1);
	if (s1->st_Flags & SF_LEA)
	    FreeRegister(&stor1);
	if (s2->st_Flags & SF_LEA)
	    FreeRegister(&stor2);
	return;
    }
    if (s1->st_Type == ST_Reg) {
	outop("cmp", 0, s2, s1);
	return;
    }
    if (s1->st_Size > 4) {
	asm_blockop(exp, "cmp", *pcond, s1, s2);
    } else {
	LockStorage(s2);
	AllocDataRegister(&stor1, s2->st_Size);
	asm_move(exp, s1, &stor1);
	UnlockStorage(s2);
	outop("cmp", 0, s2, &stor1);
	FreeRegister(&stor1);
    }
}

/*
 *  asm_div()	d = s1 / s2	divs s2,s1  divs ea,Dn
 *		d = s1 % s2
 *
 *  returns (1) TRUE if we had to make a call
 */

long
asm_div(Exp *exp, Stor *s1, Stor *s2, Stor *d, short mod)
{
    Stor ts1;
    Stor ts2;
    short ts1Flag = 0;
    short ts2Flag = 0;
    int opsize = 2;
    char *dop;

    if ((s1->st_Flags | s2->st_Flags) & SF_UNSIGNED)
	dop = (mod) ? "modu" : "divu";
    else
	dop = (mod) ? "mods" : "divs";

    /*
     *
     *	procedure call required?  means:    (1) if d long and s1 or s2 long
     *					    (2) d not long and s2 long
     */

    /*printf("; s1,s2,d %d %d %d\n", s1->st_Size, s2->st_Size, d->st_Size);*/

    if (s2->st_Size == 4 || (d->st_Size == 4 && s1->st_Size == 4)) {
	if (MC68020Opt && !mod) {
	    opsize = 4;
	} else {
	    CallAsmSupport(exp, dop, s1, s2, d, 1);
	    return(1);
	}
    }

    ts1 = *s1;
    ts2 = *s2;

    /*
     *	s1 directly addressable?  means:    (1) long
     *					    (2) in data reg
     *					    (3) s1 == d
     *
     *	(also implies d)
     *
     *	optimization:	if d is a register attempt to divide directly to it
     */

    if ((s1->st_Type != ST_Reg || s1->st_RegNo >= RB_ADDR) || s1->st_Size != 4 || SameStorage(s1, d) == 0) {
	if (d->st_Type == ST_Reg && d->st_RegNo < RB_ADDR && !SameRegister(s2, d)) {
	    ts1 = *d;
	    ts1.st_Size = 4;
	    ts1.st_Flags = s1->st_Flags;
	    asm_ext(exp, s1, &ts1, s1->st_Flags);
	} else {
	    LockStorage(s2);
	    AllocDataRegister(&ts1, 4);
	    asm_ext(exp, s1, &ts1, s1->st_Flags);
	    UnlockStorage(s2);
	    ts1Flag = 1;
	}
    }

    /*
     *	s2 addressable?     means:  (1) word sized
     *				    (2) not in addr reg
     */

    if (s2->st_Size != opsize || (s2->st_Type == ST_Reg && s2->st_RegNo >= RB_ADDR)) {
	LockStorage(s1);
	AllocDataRegister(&ts2, opsize);
	asm_ext(exp, s2, &ts2, s2->st_Flags);
	UnlockStorage(s1);
	ts2Flag = 1;
    }

    /*
     *	output instruction
     */

    dop = ((s1->st_Flags | s2->st_Flags) & SF_UNSIGNED) ? "divu" : "divs";

    outop(dop, opsize, &ts2, &ts1);

    if (mod) {
	char c = ts1.st_RegNo + '0';
	if (d->st_Size == 4 && (d->st_Flags & SF_UNSIGNED))
	    printf("\tclr.w\tD%c\n", c);
	printf("\tswap\tD%c\n", c);
	if (d->st_Size == 4 && !(d->st_Flags & SF_UNSIGNED))
	    printf("\text.l\tD%c\n", c);
    } else {
	char c = ts1.st_RegNo + '0';
	if (d->st_Size == 4 && opsize != 4) {
	    if (d->st_Flags & SF_UNSIGNED)
		printf("\tand.l\t#$FFFF,D%c\n", c);
	    else
		printf("\text.l\tD%c\n", c);
	}
    }

    /*
     *	Handle temporaries
     */

    if (ts2Flag)
	FreeRegister(&ts2);
    if (ts1Flag) {
	FreeRegister(&ts1);
	ts1.st_Size = d->st_Size;
	asm_move(exp, &ts1, d);
    }
    return(0);	    /*	no call required */
}

#ifdef NOTDEF

asm_div(exp, s1, s2, d, mod)
Exp *exp;
Stor *s1, *s2;
Stor *d;
short mod;
{
    short s2Size;
    short sv;
    int r;
    char *dop;
    char *dopc;

    if (s1->st_Flags & SF_LEA) {
	Stor t;
	LockStorage(s2);
	AllocAddrRegister(&t);
	asm_move(exp, s1, &t);
	UnlockStorage(s2);
	r = asm_div(&t, s2, d, mod);
	FreeRegister(&t);
	return(r);
    }
    if (s2->st_Flags & SF_LEA) {
	Stor t;
	LockStorage(s1);
	AllocAddrRegister(&t);
	asm_move(exp, s2, &t);
	UnlockStorage(s1);
	r = asm_div(s1, &t, d, mod);
	FreeRegister(&t);
	return(r);
    }

    if (s2->st_Type == ST_IntConst) {
	if (s2->st_IntConst == 1) {
	    if (mod)
		asm_movei(exp, 0, d);
	    else
		asm_move(exp, s1, d);
	    return(0);
	}
	if (s2->st_IntConst == -1) {
	    if (mod)
		asm_movei(exp, 0, d);
	    else
		asm_neg(exp, s1, d);
	    return(0);
	}
    }

    /*
     *	note the last condition... if d = s1/s2 where s1's size is not
     *	a long (divs/divu always take a long for the numerator), we
     *	must make it a long whether or not it is already in a data reg.
     *	We cannot simply truncate the data reg since it might be a
     *	long register variable.
     */

    if (!SameStorage(s1, d) || d->st_Type != ST_Reg || d->st_RegNo >= RB_ADDR || s1->st_Size == 2) {
	Stor t;
	Stor u;

	LockStorage(s2);
	AllocDataRegister(&t, s1->st_Size);
	t.st_Flags |= s1->st_Flags & SF_UNSIGNED;
	u = t;
	u.st_Size = d->st_Size;

	if (s1->st_Type == ST_IntConst) {
	    t.st_Size = 4;
	    asm_move(exp, s1, &t);
	} else {
	    if ((t.st_Flags & SF_UNSIGNED) && t.st_Size != 4)
		asm_movei(exp, 0, &t);
	    asm_move(exp, s1, &t);
	    if ((t.st_Flags & SF_UNSIGNED) == 0 && t.st_Size != 4) {
		if (t.st_Size == 1)
		    outop("ext", 2, NULL, &t);
		if (t.st_Size == 2)
		    outop("ext", 4, NULL, &t);
	    }
	    t.st_Size = 4;
	}
	UnlockStorage(s2);
	r = asm_div(&t, s2, &u, mod);
	asm_move(exp, &u, d);
	FreeRegister(&t);
	return(r);
    }

    s2Size = s2->st_Size;
    if (s2->st_Type == ST_IntConst)
	s2->st_Size = SizeFitSU(s2->st_IntConst, s2);

    /*
     *	s1 = s1 / s2, s1 is a DReg.  note that s1 and d might
     *	have different sizes.  s2 determines sign.
     *		s1  2,4
     *		s2  2,4 s/u
     *		d   2,4
     *
     */

    Assert(s1->st_Size != 1 && s2->st_Size != 1 && d->st_Size != 1);

    sv = (~s1->st_Size & 2) | (s2->st_Size & 4) | ((d->st_Size & 4) << 1);
    if (s2->st_Flags & SF_UNSIGNED)
	++sv;

    /*
     *	odd == unsigned divide
     *	msb 8 for d size == 32, lsb 8 for d size == 16
     *	& 4 : s2 size 32
     *	& 2 : s1 size 32
     */

    dop = (sv & 1) ? "divu" : "divs";
    if (mod)
	dopc = (sv & 1) ? "modu" : "mods";
    else
	dopc = dop;

    if (sv >= 4) {	/*  >= 8 32 bit dest,	else 16 bit dest    */
	r = 1;
	CallAsmSupport(exp, dopc, s1, s2, d, 1);
    } else {		/*  32/16 or 32%16  */
	r = 0;
	outop(dop, 2, s2, d);
	if (mod) {
	    char c = d->st_RegNo + '0';
	    if (d->st_Size == 4 && (d->st_Flags & SF_UNSIGNED))
		printf("\tclr.w\tD%c\n", c);
	    printf("\tswap\tD%c\n", c);
	    if (d->st_Size == 4 && !(d->st_Flags & SF_UNSIGNED))
		printf("\text.l\tD%c\n", c);
	} else {
	    char c = d->st_RegNo + '0';
	    if (d->st_Size == 4) {
		if (d->st_Flags & SF_UNSIGNED)
		    printf("\tand.l\t#$FFFF,D%c\n", c);
		else
		    printf("\text.l\tD%c\n", c);
	    }
	}
    }
    s2->st_Size = s2Size;
}

#endif

/*
 *  asm_mul()	d = s1 * s2	d = s1 * #const
 *
 *  Returns (1) TRUE if we had to make a call.	asm_mul_requires_call()
 *  deals with simplistic cases, erroring on the side of returning TRUE
 *  sometimes even if no call will be made, but never returning FALSE
 *  and then making a call.
 */

long
asm_mul_requires_call(offset)
long offset;
{
    if (offset >= -1 && offset <= 1)
	return(0);
    if (offset < 0)
	return(1);
    if (PowerOfTwo(offset) >= 0)
	return(0);
    return(1);
}

/*
 *  d = s1 * s2     muls ea,Dn	    muls s2,s1
 *
 *  returns (1) TRUE if we had to make a call
 */

long
asm_mul(exp, s1, s2, d)
Exp *exp;
Stor *s1, *s2;
Stor *d;
{
    Stor ts1;
    Stor ts2;
    short ts1Flag = 0;
    short ts2Flag = 0;
    int opsize = 2;
    char *mop = ((s1->st_Flags | s2->st_Flags) & SF_UNSIGNED) ? "mulu" : "muls";

    /*
     *	optimize constant placement, want constant in s2
     *	optimize destination, want destination == s1
     */

    if (s1->st_Type == ST_IntConst)
	SWAPS(s1,s2);
    if (SameStorage(s2,d))
	SWAPS(s1,s2);

    /*
     *	optimize for constant multiply
     */

    ts1 = *s1;
    ts2 = *s2;

    if (s2->st_Type == ST_IntConst) {
	long n;

	switch (s2->st_IntConst) {
	case -1:
	    if (s1->st_Size != d->st_Size) {
		if (d->st_Type == ST_Reg && d->st_Type < RB_ADDR) {
		    asm_ext(exp, s1, d, s1->st_Flags);
		    asm_neg(exp, d, d);
		} else {
		    AllocDataRegister(&ts1, d->st_Size);
		    asm_ext(exp, s1, &ts1, s1->st_Flags);
		    asm_neg(exp, &ts1, d);
		    FreeRegister(&ts1);
		}
	    } else {
		asm_neg(exp, s1, d);
	    }
	    return(0);
	case 0:
	    asm_movei(exp, 0, d);
	    return(0);
	case 1:
	    asm_ext(exp, s1, d, s1->st_Flags);
	    return(0);
	}

	n = PowerOfTwo(s2->st_IntConst);
	if (n >= 0) {
	    AllocConstStor(&ts1, n, &LongType);
	    asm_shift(exp, -1, s1, &ts1, d);
	    return(0);
	}
	ts2.st_Size = SizeFitSU(s2->st_IntConst, s2);
    }

    /*
     *	procedure call required?  means:    (1) either source is long
     */

    if (ts1.st_Size == 4 || ts2.st_Size == 4) {
	if (MC68020Opt) {
	    opsize = 4;
	} else {
	    CallAsmSupport(exp, mop, &ts1, &ts2, d, -1);
	    return(1);
	}
    }

    /*
     *	s1 directly addressable? means:  (1) word
     *					 (2) in data reg
     *					 (3) s1 == d
     *	(also implies d)
     *
     *	optimization:	if d is a register attempt to multiply directly
     *			to it
     */

    if ((s1->st_Type != ST_Reg || s1->st_RegNo >= RB_ADDR) || s1->st_Size != opsize || SameStorage(s1, d) == 0) {
	if (d->st_Type == ST_Reg && d->st_RegNo < RB_ADDR && !SameRegister(s2, d)) {
	    ts1 = *d;
	    ts1.st_Size = opsize;
	    ts1.st_Flags = s1->st_Flags;
	    asm_ext(exp, s1, &ts1, s1->st_Flags);
	} else {
	    LockStorage(s2);
	    AllocDataRegister(&ts1, opsize);
	    asm_ext(exp, s1, &ts1, s1->st_Flags);
	    UnlockStorage(s2);
	    ts1Flag = 1;
	}
    }

    /*
     *	s2 addressable?     means:  (1) word
     *				    (2) not in address register
     */

    if (s2->st_Size != opsize || (s2->st_Type == ST_Reg && s2->st_RegNo >= RB_ADDR)) {
	LockStorage(s1);
	AllocDataRegister(&ts2, opsize);
	asm_ext(exp, s2, &ts2, s2->st_Flags);
	UnlockStorage(s1);
	ts2Flag = 1;
    }

    /*
     *	output instruction, result is a longword
     */

    outop(mop, opsize, &ts2, &ts1);

    /*
     *	Handle temporaries
     */

    if (ts2Flag)
	FreeRegister(&ts2);
    if (ts1Flag) {
	FreeRegister(&ts1);
	ts1.st_Size = d->st_Size;
	asm_move(exp, &ts1, d);
    }
    return(0);	    /*	no call required */
}


#ifdef NOTDEF

asm_mul(exp, s1, s2, d)
Exp *exp;
Stor *s1, *s2;
Stor *d;
{
    short s2Size;
    short sv;
    int r = 0;
    char *mop;

    if (s1->st_Flags & SF_LEA) {
	Stor t;
	LockStorage(s2);
	AllocAddrRegister(&t);
	asm_move(exp, s1, &t);
	UnlockStorage(s2);
	r = asm_mul(&t, s2, d);
	FreeRegister(&t);
	return(r);
    }
    if (s2->st_Flags & SF_LEA) {
	Stor t;
	LockStorage(s1);
	AllocAddrRegister(&t);
	asm_move(exp, s2, &t);
	UnlockStorage(s1);
	r = asm_mul(s1, &t, d);
	FreeRegister(&t);
	return(r);
    }

    if (SameStorage(s2,d))
	SWAPS(s1,s2);

    if (s1->st_Type == ST_IntConst)
	SWAPS(s1,s2);

    s2Size = s2->st_Size;
    if (s2->st_Type == ST_IntConst) {
	int n;
	switch(s2->st_IntConst) {
	case -1:
	    asm_neg(s1, d);
	    return(0);
	case 0:
	    asm_movei(exp, 0, d);
	    return(0);
	case 1:
	    asm_move(exp, s1, d);
	    return(0);
	}

	n = PowerOfTwo(s2->st_IntConst);
	if (n >= 0) {
	    Stor con;
	    AllocConstStor(&con, n, &LongType);
	    asm_shift(exp, -1, s1, &con, d);
	    return(0);
	}
	s2->st_Size = SizeFitSU(s2->st_IntConst, s2);
    }


    if (!SameStorage(s1, d) || d->st_Type != ST_Reg || d->st_RegNo >= RB_ADDR) {
	Stor t;
	Stor u;

	LockStorage(s2);
	AllocDataRegister(&t, s1->st_Size);
	u = t;
	u.st_Size = d->st_Size;

	asm_move(exp, s1, &t);
	UnlockStorage(s2);
	r = asm_mul(&t, s2, &u);
	asm_move(exp, &u, d);
	FreeRegister(&t);
	s2->st_Size = s2Size;
	return(r);
    }


    /*
     *	s1 = s1 * s2, s1 is a DReg.  note that s1 and d might
     *	have different sizes.  s2 determines sign.
     *		s1  2,4
     *		s2  2,4 s/u
     *		d   2,4
     *
     */

    Assert(s1->st_Size != 1 && s2->st_Size != 1 && d->st_Size != 1);

    sv = (~s1->st_Size & 2) | (s2->st_Size & 4) | ((d->st_Size & 4) << 1);
    if (s2->st_Flags & SF_UNSIGNED)
	++sv;

    /*
     *	odd == unsigned multiply
     *	msb 8 for d size == 32, lsb 8 for d size == 16
     *	& 4 : s2 size 32
     *	& 2 : s1 size 32
     */

    mop = (sv & 1) ? "mulu" : "muls";

    if (sv <= 9) {
	/*  s/u  ? * ? -> 16	*/
	Stor t = *s2;
	if (t.st_Type != ST_Reg && (sv & 4))
	    t.st_Offset += 2;
	outop(mop, 2, &t, d);
	r = 0;
    } else {
	/*  s 32 * 32 -> 32   */
	/*  u 32 * 32 -> 32   */

	CallAsmSupport(exp, mop, s1, s2, d, -1);
	r = 1;
    }
    s2->st_Size = s2Size;
    return(r);
}

#endif

void
asm_shift(exp, dir, s1, s2, d)
Exp *exp;
long dir;
Stor *s1;
Stor *s2;
Stor *d;
{
    char *str = "asl";
    static short Recur = 3;

    if (--Recur == 0)
    {
	dbprintf(("asm_shift recur"));
	Assert(0);
    }

    if (dir > 0) {
	if (s1->st_Flags & SF_UNSIGNED)
	    str = "lsr";
	else
	    str = "asr";
    }

    /*
     *	XXX optimize 1 << ?
     */


    if (!SameStorage(s1, d) || SameStorage(s2, d) || s1->st_Size != d->st_Size || (s1->st_Type == ST_Reg && s1->st_RegNo >= RB_ADDR)) {
	Stor nd;

wc:
	LockStorage(s2);
	AllocDataRegister(&nd, Max(s1->st_Size, d->st_Size));
	nd.st_Flags |= s1->st_Flags & SF_UNSIGNED;
	asm_move_cast(exp, s1, &nd);
	UnlockStorage(s2);
	asm_shift(exp, dir, &nd, s2, &nd);
	asm_move_cast(exp, &nd, d);
	FreeRegister(&nd);
	++Recur;
	return;
    }
    /*
     *	s1 == d
     */
    if (s1->st_Type != ST_Reg) {
	if (s2->st_Type == ST_IntConst && s2->st_IntConst == 1 && d->st_Size == 2) {
	    outop(str, 0, NULL, d);
	    ++Recur;
	    return;
	}
	/*puts("SNR");*/
	goto wc;
    }

    /*
     *	s1 == d == register, s2 integer in range?
     */
    if (s2->st_Type == ST_IntConst && s2->st_IntConst > 0 && s2->st_IntConst <= 8) {
	outop(str, 0, s2, d);
	++Recur;
	return;
    }

    /*
     *	s2 out of range, stick in register too
     */

    if (s2->st_Type == ST_Reg && s2->st_RegNo < RB_ADDR) {
	outop(str, 0, s2, d);
    } else {
	Stor cnt;

	AllocDataRegister(&cnt, s2->st_Size);
	asm_move(exp, s2, &cnt);
	outop(str, 0, &cnt, d);
	FreeRegister(&cnt);
    }
    ++Recur;
}

/*
 *  Compiler Support call (passing simple data <= 4 bytes)
 *
 *	order=1     order=0	order=-1
 *  s1	    D0	      D1	either way
 *  s2	    D1	      D0
 *
 *  (call)
 *  D0->d
 *
 *  handle byte/word cases (ext into long)
 */

void
CallAsmSupport(Exp *exp,char *mop, Stor *s1, Stor *s2, Stor *d, short orderReq)
{
    Stor rd0, rd1;
    Stor ss1;
    Stor ss2;
    ulong mask = 0;

    GenFlagCallMade();

    if (d)
    {
	if (d->st_Type == ST_Reg) {
	    mask = 1 << d->st_RegNo;
	} else if (d->st_Type == ST_RegIndex) {
	    if (((1 << d->st_RegNo) | (1 << d->st_RegNo2)) & REGSCRATCH)
	    {
		dbprintf(("scratch overrun %08lx\n", mask));
		Assert(0);
	    }
	}
    }

    if ((mask = ~mask & GetLockedScratch()) != 0) {
	asm_save_regs(mask);
	printf("; Saved locked: %08lx\n", mask);
    }

    AllocDataRegisterAbs(&rd0, 4, RB_D0);
    AllocDataRegisterAbs(&rd1, 4, RB_D1);

    AddAuxSub(mop);

    if (s1 && s1->st_Type == ST_FltConst) {
	long fpv;

	Assert(s1->st_Size == 4);
	asm_fltconst(exp, s1, &fpv);
	AllocConstStor(&ss1, fpv, &LongType);
	s1 = &ss1;
    }
    if (s2 && s2->st_Type == ST_FltConst) {
	long fpv;

	Assert(s2->st_Size == 4);
	asm_fltconst(exp, s2, &fpv);
	AllocConstStor(&ss2, fpv, &LongType);
	s2 = &ss2;
    }

    if (s1 && s2) {
	if (SameRegister(s2, &rd0)) {	    /*	s2 uses D0! */
	    if (SameRegister(s1, &rd1)) {   /*	s1 uses D1! */
		/*
		 *  put in reversed registers
		 */

		asm_ext(exp, s1, &rd1, s1->st_Flags);
		asm_ext(exp, s2, &rd0, s2->st_Flags);
		if (orderReq > 0)
		    printf("\texg\tD0,D1\n");
	    } else {
		if (orderReq > 0) {
		    asm_ext(exp, s2, &rd1, s2->st_Flags);
		    asm_ext(exp, s1, &rd0, s1->st_Flags);
		} else {
		    asm_ext(exp, s2, &rd0, s2->st_Flags);
		    asm_ext(exp, s1, &rd1, s1->st_Flags);
		}
	    }
	} else {
	    asm_ext(exp, s1, &rd0, s1->st_Flags);
	    asm_ext(exp, s2, &rd1, s2->st_Flags);
	    if (orderReq == 0)
		printf("\texg\tD0,D1\n");
	}
    } else if (s1) {
	asm_ext(exp, s1, &rd0, s1->st_Flags);
    } else if (s2) {
	asm_ext(exp, s2, &rd1, s2->st_Flags);
    }

    if (SmallCode)
	printf("\tjsr\t__%s(pc)\n", mop);
    else
	printf("\tjsr\t__%s\n", mop);

    if (d) {
	if (d->st_Type != ST_Reg || d->st_RegNo != RB_D0) {
	    rd0.st_Size = d->st_Size;
	    asm_move(exp, &rd0, d);
	    /*printf("\tmove.l\tD0,%s\n", StorToString(d, NULL));*/
	}
    }

    FreeRegister(&rd0);
    FreeRegister(&rd1);

    asm_restore_regs(mask);
}

/*
 *  BITFIELDS
 */

#ifdef REGISTERED

void
asm_bfext(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    Type *t;
    Stor dtmp;
    Stor stmp;
    short tmpFlag;

    OptimizeBitField(s, &stmp);


    LockStorage(&stmp);
    if (d->st_Type==ST_Reg && d->st_RegNo<RB_ADDR && !SameRegister(&stmp, d)){
	dtmp = *d;
	tmpFlag = 0;
    } else {
	AllocDataRegister(&dtmp, d->st_Size);
	tmpFlag = 1;
    }
    UnlockStorage(&stmp);

    Assert(stmp.st_Size > 1);

    if (stmp.st_Flags & SF_UNSIGNED) {
	switch(stmp.st_Size) {
	case 2:
	    t = &UShortType;
	    break;
	default:
	    t = &ULongType;
	    break;
	}
    } else {
	switch(stmp.st_Size) {
	case 2:
	    t = &ShortType;
	    break;
	default:
	    t = &LongType;
	    break;
	}
    }

    dbprintf(("BitFieldExt %s[siz=%ld] ->", StorToString(&stmp, NULL), stmp.st_Size));
    dbprintf(("%s (off=%d, siz=%d)\n", StorToString(&dtmp, NULL), stmp.st_BOffset, stmp.st_BSize));
    Assert(stmp.st_Flags & SF_BITFIELD);

    /*
     *	extract a bit field.  A 1 bit bitfield is extracted via a btst/scc/neg
     */

    if (stmp.st_BSize == 1) {
	Stor st;

	AllocConstStor(&st, 1 << stmp.st_BOffset, t);
	st.st_Size = stmp.st_Size;
	if (stmp.st_Flags & SF_UNSIGNED) {
	    if (dtmp.st_Size != 1)
		asm_movei(exp, 0, &dtmp);
	    asm_test_and(exp, &stmp, &st);
	    asm_sccb(exp, &dtmp, COND_NEQ, 0);
	} else {
	    asm_test_and(exp, &stmp, &st);
	    asm_sccb(exp, &dtmp, COND_NEQ, 1);
	    if (dtmp.st_Size != 1) {
		st = dtmp;
		st.st_Size = 1;
		asm_ext(exp, &st, &dtmp, 0);
	    }
	}
    } else {
	Stor st;
	short d_size = dtmp.st_Size;
	long mask;

	/*
	 *  unsigned breakout optimization
	 */

	dtmp.st_Size = stmp.st_Size;
	mask = ((ulong)-1 >> (32 - stmp.st_BSize)) << stmp.st_BOffset;

	/* if (stmp.st_Flags & SF_UNSIGNED) { */
	    if (dtmp.st_Size != 4)
		asm_movei(exp, 0, &dtmp);
	/* } */
	AllocConstStor(&st, mask, t);		    /*	mask source -> d */
	asm_and(exp, &stmp, &st, &dtmp);

	if (stmp.st_BOffset) {			    /*	shift it	*/
	    long flags = dtmp.st_Flags;

	    dtmp.st_Flags |= SF_UNSIGNED;
	    AllocConstStor(&st, stmp.st_BOffset, t);
	    asm_shift(exp, 1, &dtmp, &st, &dtmp);
	    dtmp.st_Flags = flags;
	}

	/*
	 *  if signed then sign-extend, else if unsigned done (0 fill
	 *  above handles word case).  When signed, we must ensure that
	 *  the entire result type is set negative, so we restore the
	 *  destination size immediately.
	 */

	if ((stmp.st_Flags & SF_UNSIGNED) == 0) {
	    long l1 = AllocLabel();

	    AllocConstStor(&st, 1 << (stmp.st_BSize - 1), t);
	    asm_test_and(exp, &st, &dtmp);
	    asm_condbra(COND_EQ, l1);	/*  skip if not negative */
					/*  if negative, make so */
	    AllocConstStor(&st, -1 << stmp.st_BSize, t);
	    dtmp.st_Size = d_size;
	    st.st_Size = d_size;
	    asm_or(exp, &st, &dtmp, &dtmp);
	    asm_label(l1);
	} else {
	    dtmp.st_Size = d_size;
	}
    }
    if (tmpFlag) {
	asm_move(exp, &dtmp, d);
	FreeRegister(&dtmp);
    }
}

void
asm_bfsto(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    long mask;
    Type *t;
    Stor dtmp;

    OptimizeBitField(d, &dtmp);

    mask = ((ulong)-1 >> (32 - dtmp.st_BSize)) << dtmp.st_BOffset;
    t = (dtmp.st_Size == 2) ? &UShortType : &ULongType;


    dbprintf(("BitFieldSto %s ->", StorToString(s, NULL)));
    dbprintf(("%s (off=%d, siz=%d)\n", StorToString(&dtmp, NULL), dtmp.st_BOffset, dtmp.st_BSize));
    Assert(dtmp.st_Flags & SF_BITFIELD);

    /*
     *	store into a bit field.
     */

    if (s->st_Type == ST_IntConst) {
	long valu;
	Stor st;

	valu = mask & (s->st_IntConst << dtmp.st_BOffset);

	dbprintf(("valu %08lx\nmask %08lx\n", valu, mask));

	if (valu == 0) {
	    AllocConstStor(&st, ~mask, t);
	    asm_and(exp, &st, &dtmp, &dtmp);
	} else if (valu == mask) {
	    AllocConstStor(&st, mask, t);
	    asm_or(exp, &st, &dtmp, &dtmp);
	} else {
	    AllocConstStor(&st, ~mask, t);
	    asm_and(exp, &st, &dtmp, &dtmp);
	    AllocConstStor(&st, valu, t);
	    asm_or(exp, &st, &dtmp, &dtmp);
	}
    } else {
	Stor st;
	Stor tm;

	if (dtmp.st_BSize == 1) {     /*  test bit 0, bset/bclr   */
	    long l1 = AllocLabel();
	    long l2 = AllocLabel();

	    AllocConstStor(&st, 1, t);
	    st.st_Size = s->st_Size;
	    asm_test_and(exp, &st, s);
	    asm_condbra(COND_EQ, l1);
	    AllocConstStor(&st, mask, t);
	    asm_or(exp, &st, &dtmp, &dtmp);
	    asm_branch(l2);
	    asm_label(l1);
	    AllocConstStor(&st, ~mask, t);
	    asm_and(exp, &st, &dtmp, &dtmp);
	    asm_label(l2);
	} else {
	    Stor t2;
	    AllocDataRegister(&tm, dtmp.st_Size);
	    AllocDataRegister(&t2, dtmp.st_Size);

	    if (s->st_Size < tm.st_Size) {
		asm_ext(exp, s, &tm, s->st_Flags);   /*  ext & mask	 */
	    } else {
		tm.st_Size = s->st_Size;
		asm_move(exp, s, &tm);
		tm.st_Size = dtmp.st_Size;
	    }

	    AllocConstStor(&st, mask >> dtmp.st_BOffset, t);
	    asm_and(exp, &st, &tm, &tm);

	    if (dtmp.st_BOffset) {			  /*  shift it	      */
		AllocConstStor(&st, dtmp.st_BOffset, t);
		asm_shift(exp, 0, &tm, &st, &tm);
	    }
	    AllocConstStor(&st, ~mask, t);
	    asm_and(exp, &st, &dtmp, &t2);		   /*  mask destination    */
	    asm_or(exp, &tm, &t2, &dtmp);		   /*  or in changes	   */

	    FreeRegister(&t2);
	    FreeRegister(&tm);
	}
    }
}

void
asm_bftst(exp, s)
Exp *exp;
Stor *s;
{
    Stor st;
    Stor stmp;
    long mask;
    Type *t;

    OptimizeBitField(s, &stmp);
    t = (stmp.st_Size == 2) ? &UShortType : &ULongType;

    dbprintf(("BitFieldTst %s (off=%d, siz=%d)\n", StorToString(&stmp, NULL), stmp.st_BOffset, stmp.st_BSize));

    /*
     *	test a bit field.  A 1 bit bitfield is tested with btst
     */

    mask = ((ulong)-1 >> (32 - stmp.st_BSize)) << stmp.st_BOffset;
    AllocConstStor(&st, mask, t);
    asm_test_and(exp, &stmp, &st);
}

void
OptimizeBitField(s, d)
Stor *s;
Stor *d;
{
    *d = *s;

    if (d->st_Size == 4) {
	if (d->st_BOffset < 16 && d->st_BOffset + d->st_BSize < 16) {
	    d->st_Size = 2;
	    d->st_Offset += 2;
	} else if (d->st_BOffset > 16) {
	    d->st_BOffset -= 16;
	    d->st_Size = 2;
	}
    }
}

#else

void
asm_bfext(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    cerror(EUNREG, "bitfields");
}

void
asm_bfsto(exp, s, d)
Exp *exp;
Stor *s;
Stor *d;
{
    cerror(EUNREG, "bitfields");
}

void
asm_bftst(exp, s)
Exp *exp;
Stor *s;
{
    cerror(EUNREG, "bitfields");
}

#endif

void
asm_illegal(void)
{
    puts("\tdc.w\t$4AFC");
}
