/* hardop.c - hardware operations for bcc */

/* Copyright (C) 1992 Bruce Evans */

#include "const.h"
#include "types.h"
#include "byteord.h"
#include "condcode.h"
#include "gencode.h"
#include "reg.h"
#include "sc.h"
#include "scan.h"
#include "sizes.h"
#include "type.h"

FORWARD void sub1 P((struct symstruct *source, struct symstruct *target));

PUBLIC void add(source, target)
struct symstruct *source;
struct symstruct *target;
{
    scalar_t sscalar;

    if (source->indcount == 0 && source->storage != CONSTANT &&
	(target->indcount != 0 || target->storage & ALLDATREGS))
	swapsym(target, source);
    if ((sscalar = source->type->scalar) & DLONG)
    {
	longop(ADDOP, source, target);
	return;
    }
    if (sscalar & RSCALAR)
    {
	floatop(ADDOP, source, target);
	return;
    }
    if (source->storage == CONSTANT)
    {
	extend(target);
	if (target->indcount != 0 || target->storage & reguse)
	    loadany(target);
	target->offset.offi += (offset_t) source->offset.offv;
    }
    else if (source->indcount == 0)
    {
	/* target is also direct */
	sscalar |= target->type->scalar;	/* remember if unsigned/long */
	target->type = pctype;	/* fake to use indexadr() */
	indexadr(source, target);
    }
    else
    {
	/* source is indirect and not DREG */
	extend(target);
	load(target, DREG);
	outadd();
	movereg(source, DREG);
	if (source->type->scalar & CHAR)
	    adc0();
    }
    target->type = iscalartotype(target->type->scalar | sscalar);
}

PUBLIC void incdec(op, source)
op_pt op;
struct symstruct *source;
{
    offset_t bump;
    bool_t postflag;
    store_t regmark;
    struct symstruct *target;
    struct symstruct targ;
#ifdef MC6809
    store_pt targreg;
#endif

    *(target = &targ) = *source;
    bump = 1;
    if (targ.type->constructor & POINTER)
	bump = targ.type->nexttype->typesize;
    if (op == PREDECOP || op == POSTDECOP)
	bump = -bump;
    postflag = FALSE;
    if (op == POSTDECOP || op == POSTINCOP)
	postflag = TRUE;
    reguse |= targ.storage;
    if (targ.type->scalar & DLONG)	/* cannot be direct */
    {
	if (postflag)
	{
	    regmark = reguse;
	    if (((reguse |= OPREG) & allindregs) == allindregs)
	    {
		saveopreg();
		load(source, OPREG);
		pushreg(source->storage);
		restoreopreg();
	    }
	    else
	    {
		loadany(source);
		reguse = regmark | source->storage;
		saveopreg();
	    }
	}
	else
	    saveopreg();
	pointat(target);
	switch (op)
	{
	case PREDECOP:
	case POSTDECOP:
	    call("ldec");
	    break;
	case PREINCOP:
	case POSTINCOP:
	    call("linc");
	    break;
	}
	outlongendian();
	restoreopreg();
	if (postflag && source->storage == OPREG)
	    poplist(OPREG);
	return;
    }
    if (targ.type->scalar & RSCALAR)
    {
	saveopreg();
	pointat(target);
	switch (op)
	{
	case PREDECOP:
	    call("Fpredec");
	    break;
	case PREINCOP:
	    call("Fpreinc");
	    break;
	case POSTDECOP:
	    call("Fpostdec");
	    break;
	case POSTINCOP:
	    call("Fpostinc");
	    break;
	}
	outntypechar(targ.type);
	restoreopreg();
	if (postflag)
	{
	    justpushed(source);
	    source->type = targ.type;
	}
	return;
    }
    loadany(source);
#ifdef MC6809
    if (postflag && targ.flags != REGVAR &&
	!(source->storage & ALLDATREGS) &&
	((reguse |= source->storage) & allindregs) != allindregs)
    {
	targreg = getindexreg();
	outlea();
	outregname(targreg);
	outtab();
	outshex(bump);
	outncregname(source->storage);
    }
    else
    {
	addconst(bump, targreg = source->storage);
	if (postflag)
	    source->offset.offi = -bump;
    }
    storereg(targreg, target);
    target->storage = targreg;
    target->offset.offi = 0;
#else
    addconst(bump, source->storage);
    if (postflag)
	source->offset.offi = -bump;
    storereg(source->storage, target);
    target->storage = source->storage;
    target->offset.offi = 0;
#endif
}

PUBLIC void neg(target)
struct symstruct *target;
{
    scalar_t scalar;
    struct symstruct *source;

    if ((scalar = target->type->scalar) & DLONG)
	long1op(NEGOP, target);
    else if (scalar & RSCALAR)
	float1op(NEGOP, target);
    else
    {
	if (scalar & SHORT)
	    extend(target);
	if (!(target->storage & ALLDATREGS))
	{
	    /* load 0, subtract is shorter than load, negate */
	    /* if no hardware integer negate; about the same if there is */
	    sub1(target, source = constsym((value_t) 0));
	    *target = *source;
	}
	else
	{
	    load(target, DREG);
	    negreg(target->storage);
	    target->storage = DREG;
	}
	target->type = iscalartotype(scalar);
    }
}

PUBLIC void not(target)
struct symstruct *target;
{
    if (target->type->scalar & DLONG)
	long1op(NOTOP, target);
    else
    {
	extend(target);
	load(target, DREG);
	comDreg();
    }
}

/* 1-byte ops like AND acting on integers (not both constant) */

PUBLIC void op1(op, source, target)
op_pt op;
struct symstruct *source;
struct symstruct *target;
{
    char *opstr;
#ifdef OP1
# if MAXINDIRECT > 1
    indn_t indcount;
# endif
#endif
    bool_t resultchar;
    scalar_t resultscalar;
    scalar_t sscalar;
    scalar_t tscalar;

    if ((sscalar = source->type->scalar) & DLONG)
    {
	longop(op, source, target);
	return;
    }
    /* Emergency fix. The types of constants should be reduced here and in
     * other low-level routines anyway, and not in exptree.c and table.c,
     * and for the 80386 and maybe the 8086 they would be better not
     * reduced.
     */
    if (source->storage == CONSTANT && ischarconst(source->offset.offv))
    {
	if (sscalar & UNSIGNED)
	    source->type = uctype;
	else
	    source->type = ctype;
	sscalar = source->type->scalar;
    }
    tscalar = target->type->scalar;
    if (target->storage == CONSTANT && ischarconst(target->offset.offv))
    {
	if (sscalar & UNSIGNED)
	    target->type = uctype;
	else
	    target->type = ctype;
	tscalar = target->type->scalar;
    }
    resultscalar = sscalar | tscalar;
    if (op != ANDOP)
	resultchar = sscalar & tscalar & CHAR;
    else if ((resultchar = (sscalar | tscalar) & CHAR) != 0 &&
	     source->storage == CONSTANT)
    {
	source->offset.offv &= CHMASKTO;
	if (sscalar & UNSIGNED)
	    source->type = uctype;
	else
	    source->type = ctype;
	sscalar = source->type->scalar;
    }
    if (target->indcount != 0 &&
	(tscalar & CHAR && !(sscalar & CHAR) &&
       op != ANDOP || source->indcount == 0 && source->storage != CONSTANT))
    {
	swapsym(target, source);
	sscalar = tscalar;
	tscalar = target->type->scalar;
    }
    if (source->indcount == 0 && source->storage != CONSTANT)
    {
	loadpres(source, target);
	push(source);
    }
#if MAXINDIRECT > 1
    else if (source->indcount >= MAXINDIRECT && !(sscalar & CHAR))
    {
	address(source);
	if (!(target->storage & ALLDATREGS))
	    preserve(target);
	load(source, getindexreg());
	indirec(source);
    }
#endif
    if (!(tscalar & CHAR) &&
	op == ANDOP && sscalar & CHAR && target->indcount == 1)
	cast(ctype, target);
    if (!(target->storage & ALLDATREGS) || target->indcount != 0)
	pres2(target, source);
    load(target, DREG);
    opstr = opstring(op);
    if (source->storage == CONSTANT && op == ANDOP)
	andconst((offset_t) source->offset.offv);
#ifdef OP1
    else if (tscalar & CHAR && !(sscalar & CHAR) && op != ANDOP)
	outload();
    else
	outop2str(opstr);
#else /* OP1 */
    else
    {
	if (tscalar & CHAR && !(sscalar & CHAR) && op != ANDOP)
	    extend(target);
	outop2str(opstr);
    }
#endif /* OP1 */
    if (source->storage == CONSTANT)
    {
	if (op != ANDOP)
	{
#ifdef OP1
	    if (!(sscalar & CHAR))
	    {
		outhiaccum();
		outncimmadr((offset_t) ((uoffset_t) source->offset.offv
					>> (INT16BITSTO - CHBITSTO)));
		outop2str(opstr);
	    }
	    outregname(BREG);
	    outncimmadr((offset_t) source->offset.offv & CHMASKTO);
#else /* OP1 */
	    if (!(sscalar & CHAR))
	    {
		outregname(DREG);
		bumplc();
	    }
	    else
		outregname(BREG);
# ifdef I8088
	    if (i386_32 && !(sscalar & CHAR))
		bumplc2();
# endif
	    outncimmadr((offset_t) source->offset.offv);

#endif /* OP1 */
	}
    }
    else if (sscalar & CHAR)
    {
	outregname(BREG);
	outopsep();
	outadr(source);
    }
    else
    {
#ifdef MC6809
	source->type = ctype;	/* fool outadr to avoid ,S++ */
#endif
#ifdef OP1
	if (!(tscalar & CHAR) || op != ANDOP)
	{
	    outhiaccum();
# if MAXINDIRECT > 1
	    indcount = source->indcount;
# endif
	    outopsep();
# if BIG_ENDIAN == 0
	    ++source->offset.offi;
# endif
	    outadr(source);
# if BIG_ENDIAN == 0
	    --source->offset.offi;
# endif
# if MAXINDIRECT > 1
	    source->indcount = indcount;
# else
	    source->indcount = 1;
# endif
	    outop2str(opstr);
	}
	outregname(BREG);
	outopsep();
# if BIG_ENDIAN
	++source->offset.offi;
# endif
	outadr(source);
    }
#else /* OP1 */
	if (!(tscalar & CHAR) || op != ANDOP)
	    outregname(DREG);
	else
	    outregname(BREG);
	outopsep();
	outadr(source);
    }
#endif /* OP1 */
    if (resultchar)
    {
	target->storage = BREG;
	if (resultscalar & UNSIGNED)
	    target->type = uctype;
	else
	    target->type = ctype;
    }
    else
    {
	target->storage = DREG;
	target->type = iscalartotype(resultscalar);
    }
}

PUBLIC void ptrsub(source, target)
struct symstruct *source;
struct symstruct *target;
{
    label_t exitlab;
    uoffset_t factor;
    label_t usignlab;

    if (source->indcount == 0 && source->storage != CONSTANT)
    {
	loadpres(source, target);
	push(source);
    }
    if (target->indcount == 0)
    {
	pres2(target, source);
	load(target, DREG);
    }
    factor = target->type->nexttype->typesize;
    source->type = target->type = itype;
    sub1(source, target);
    if (factor != 1)
    {
	pushlist(CCREG);
	sbranch(HS, usignlab = getlabel());	/* HS == no carry */
	negDreg();
	outnlabel(usignlab);
	target->type = uitype;
	softop(DIVOP, constsym((value_t) factor), target);
	target->type = itype;
	poplist(CCREG);
	sbranch(HS, exitlab = getlabel());
	negDreg();
	outnlabel(exitlab);
    }
}

PUBLIC void sub(source, target)
struct symstruct *source;
struct symstruct *target;
{
    scalar_t sscalar;

    if ((sscalar = source->type->scalar) & DLONG)
    {
	longop(SUBOP, source, target);
	return;
    }
    if (sscalar & RSCALAR)
    {
	floatop(SUBOP, source, target);
	return;
    }
    if (source->storage == CONSTANT)
    {
	extend(target);
	if (target->indcount != 0 || target->storage & reguse)
	    loadany(target);
	target->offset.offi -= (offset_t) source->offset.offv;
    }
    else
	sub1(source, target);
    target->type = iscalartotype(target->type->scalar | sscalar);
}

PRIVATE void sub1(source, target)
struct symstruct *source;
struct symstruct *target;
{
    if (source->storage == CONSTANT)
	source->type = itype;
    else if (source->indcount == 0)
    {
	loadpres(source, target);
	push(source);
    }
    extend(target);
    load(target, DREG);
    outsub();
    movereg(source, DREG);
    if (source->type->scalar & CHAR)
	sbc0();
}
