/* glogcode.c - generate code for logical expressions for bcc */

/* Copyright (C) 1992 Bruce Evans */

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

#define cc_signed(cc) ((cc) >= 4 && (cc) < 8)

PRIVATE char oppcc[] =		/* opposite condition codes LT --> GE etc */
/*  EQ, NE, RA, RN, LT, GE, LE, GT, LO, HS, LS, HI,  indices */
{
    NE, EQ, RN, RA, GE, LT, GT, LE, HS, LO, HI, LS,
};

PRIVATE char reverscc[] =	/* reverse condition codes LT --> GT etc */
{
    EQ, NE, RN, RA, GT, LE, GE, LT, HI, LS, HS, LO,
};

PRIVATE char testcc[] =		/* test condition codes LS --> EQ etc */
{
    EQ, NE, RA, RN, LT, GE, LE, GT, RN, RA, EQ, NE,
};

PRIVATE char unsigncc[] =	/* unsigned condition codes LT --> LO etc */
{
    EQ, NE, RA, RN, LO, HS, LS, HI, LO, HS, LS, HI,
};

FORWARD void cmplocal P((struct symstruct *source, struct symstruct *target,
			  ccode_t *pcondtrue));
#ifdef MC6809
FORWARD void cmporsub P((struct symstruct *target));
FORWARD bool_pt cmpsmallconst P((value_t intconst, struct symstruct *target,
				  ccode_t *pcondtrue));
#endif
FORWARD void comparecond P((struct nodestruct *exp, label_t truelab,
			     label_t falselab, bool_pt nojump));
FORWARD void jumpcond P((struct nodestruct *exp, label_t truelab,
			  label_t falselab, bool_pt nojump));
FORWARD void loadlogical P((struct symstruct *source, label_t falselab));
FORWARD void logandcond P((struct nodestruct *exp, label_t truelab,
			    label_t falselab, bool_pt nojump));
FORWARD void logorcond P((struct nodestruct *exp, label_t truelab,
			   label_t falselab, bool_pt nojump));
FORWARD void reduceconst P((struct symstruct *source));
FORWARD void test P((struct symstruct *target, ccode_t *pcondtrue));
FORWARD void testcond P((struct nodestruct *exp, label_t truelab,
			  label_t falselab, bool_pt nojump));

PUBLIC void cmp(source, target, pcondtrue)
struct symstruct *source;
struct symstruct *target;
ccode_t *pcondtrue;
{
    label_t falselab;

    cmplocal(source, target, pcondtrue);
#if 0
#ifdef I8088
    if (i386_32)
    {
	if (*pcondtrue == LO)
	{
	    getlabel();
	    getlabel();
	    outnop2str("sbb\teax,eax");
	    outnop1str("inc eax");
	    target->storage = BREG;
	    target->type = ctype;
	    return;
	}
	if (*pcondtrue == HS)
	{
	    getlabel();
	    getlabel();
	    outnop2str("sbb\teax,eax");
	    outnop2str("neg eax");
	    target->storage = BREG;
	    target->type = ctype;
	    return;
	}
    }
#endif
#endif
    sbranch(oppcc[*pcondtrue], falselab = getlabel());
    loadlogical(target, falselab);
}

PRIVATE void cmplocal(source, target, pcondtrue)
struct symstruct *source;
struct symstruct *target;
ccode_t *pcondtrue;
{
    scalar_t sscalar;
    scalar_t tempscalar;
    scalar_t tscalar;

    reduceconst(source);
    reduceconst(target);
    sscalar = source->type->scalar;
    tscalar = target->type->scalar;
    if (source->storage != CONSTANT &&
	(target->storage == CONSTANT ||
	 sscalar & CHAR && !(tscalar & CHAR) ||
	 (sscalar & CHAR || !(tscalar & CHAR)) &&
	 source->indcount == 0 && target->indcount != 0) ||
	tscalar & DLONG && target->indcount != 0)
    {
	swapsym(target, source);
	*pcondtrue = reverscc[*pcondtrue];
	tempscalar = sscalar;
	sscalar = tscalar;
	tscalar = tempscalar;
    }
    if (sscalar & CHAR && tscalar & CHAR &&
	(source->type != sctype || target->type != sctype) ||
	(sscalar | tscalar) & UNSIGNED ||
	(source->type->constructor | target->type->constructor) &
	(ARRAY | POINTER))
	*pcondtrue = unsigncc[*pcondtrue];
    if (source->type->scalar & DLONG)
    {
	longop(EQOP, source, target);
	return;
    }
    if (source->type->scalar & RSCALAR)
    {
	floatop(EQOP, source, target);
	return;
    }
    if (source->storage == CONSTANT)
    {
	if (sscalar & CHAR)
	{
	    if (tscalar & CHAR)
		*pcondtrue = unsigncc[*pcondtrue];
	    else
	    {
		source->type = iscalartotype(sscalar);
		sscalar = source->type->scalar;
	    }
	}
	if (source->offset.offv == 0)
	{
	    test(target, pcondtrue);
	    return;
	}
#ifdef MC6809
	if (cmpsmallconst(source->offset.offv, target, pcondtrue))
	    return;
#endif
    }
    if (!(sscalar & CHAR) && tscalar & CHAR)
    {
	loadpres(target, source);
	extend(target);
    }
#ifndef MC6809
# define posindependent 0
#endif
    if (source->indcount == 0 && source->storage != CONSTANT &&
	(posindependent || source->storage != GLOBAL))
    {
	loadpres(source, target);
#ifdef MC6809
	push(source);
#endif
    }
    loadpres(target, source);
#ifdef MC6809
    cmporsub(target);
#else
    outcmp();
#endif
#ifdef I8088
    if (source->storage == GLOBAL && source->indcount == 0 &&
	!(target->storage & (AXREG | ALREG)))
	bumplc();
#endif
    movereg(source, target->storage);
}

#ifdef MC6809

PRIVATE void cmporsub(target)
struct symstruct *target;
{
    if (target->storage & ALLDATREGS)
	outsub();
    else
    {
	outcmp();
	if (target->storage != XREG)
	    bumplc();
    }
}

PRIVATE bool_pt cmpsmallconst(intconst, target, pcondtrue)
value_t intconst;
struct symstruct *target;
ccode_t *pcondtrue;
{
    store_pt targreg;

    if ((*pcondtrue == EQ || *pcondtrue == NE) &&
	!(target->storage & ALLDATREGS) && !(target->type->scalar & CHAR) &&
	isnegbyteoffset(intconst) &&
	(reguse & (XREG | YREG)) != (XREG | YREG))
    {
	targreg = XREG;
	if (reguse & XREG)
	    targreg = YREG;
	if (target->indcount != 0)
	    load(target, targreg);
	target->offset.offi -= (offset_t) intconst;
	loadreg(target, targreg);
	return TRUE;
    }
    return FALSE;
}

#endif

PRIVATE void comparecond(exp, truelab, falselab, nojump)
struct nodestruct *exp;
label_t truelab;
label_t falselab;
bool_pt nojump;			/* NB if nonzero, is ~0 so complement is 0 */
{
    ccode_t condtrue;
    store_t regmark;
    struct symstruct *source;
    offset_t spmark;
    struct symstruct *target;

    regmark = reguse;
    spmark = sp;
    bileaf(exp);
    target = exp->left.nodeptr->left.symptr;
    source = exp->right->left.symptr;	/* exp->right != NULL since cond */
    switch (exp->tag)
    {
    case GEOP:
	condtrue = GE;
	break;
    case GTOP:
	condtrue = GT;
	break;
    case LEOP:
	condtrue = LE;
	break;
    case EQOP:
	condtrue = EQ;
	break;
    case LTOP:
	condtrue = LT;
	break;
    case NEOP:
	condtrue = NE;
	break;
    }
    cmplocal(source, target, &condtrue);
    changesp(spmark, FALSE);
    reguse = regmark;
    if ((bool_t) nojump)
	lbranch(oppcc[condtrue], falselab);
    else
	lbranch(condtrue, truelab);
}

PUBLIC void condop(exp)
struct nodestruct *exp;
{
    label_t exitlab;
    label_t falselab;
    struct nodestruct *falsenode;
    struct symstruct *falsesym;
    label_t truelab;
    struct nodestruct *truenode;
    struct symstruct *truesym;

    jumpcond(exp->left.nodeptr, truelab = getlabel(),
	     falselab = getlabel(), ~0);
    deflabel(truelab);
    makeleaf(truenode = exp->right->left.nodeptr);
    loadany(truesym = truenode->left.symptr);
    if (truesym->storage & reguse)
    {
	/* This can happen if truesym was a reg variable. */
	if (truesym->type->scalar & RSCALAR)
	    /* XXX - always happens for non-386 with 2 regs vars assigned. */
	    bugerror("loaded float or double into used reg");
	load(truesym, DREG);
    }
    falsenode = exp->right->right;
    if (/* falsenode->tag != LEAF || XXX */
	truesym->type != falsenode->left.symptr->type)
	cast(truenode->nodetype == falsenode->nodetype ?
	     truenode->nodetype : exp->nodetype, truesym);
    jump(exitlab = getlabel());
    deflabel(falselab);
    makeleaf(falsenode);
    falsesym = falsenode->left.symptr;
    if (falsesym->type != truesym->type)
	cast(truesym->type, falsesym);
    load(falsesym, truesym->storage);
    deflabel(exitlab);
    exp->tag = LEAF;
    exp->left.symptr = truesym;
}

PRIVATE void jumpcond(exp, truelab, falselab, nojump)
struct nodestruct *exp;
label_t truelab;
label_t falselab;
bool_pt nojump;			/* NB if nonzero, is ~0 so complement is 0 */
{
    switch (exp->tag)
    {
    case GEOP:
    case GTOP:
    case LEOP:
    case EQOP:
    case LTOP:
    case NEOP:
	comparecond(exp, truelab, falselab, nojump);
	break;
    case LOGANDOP:
	logandcond(exp, truelab, falselab, nojump);
	break;
    case LOGNOTOP:
	jumpcond(exp->left.nodeptr, falselab, truelab, ~nojump);
	break;
    case LOGOROP:
	logorcond(exp, truelab, falselab, nojump);
	break;
    default:
	testcond(exp, truelab, falselab, nojump);
	break;
    }
}

PUBLIC void jumpfalse(exp, label)
struct nodestruct *exp;
label_t label;
{
    label_t truelab;

    jumpcond(exp, truelab = getlabel(), label, ~0);
    deflabel(truelab);
}

PUBLIC void jumptrue(exp, label)
struct nodestruct *exp;
label_t label;
{
    label_t falselab;

    jumpcond(exp, label, falselab = getlabel(), 0);
    deflabel(falselab);
}

PRIVATE void loadlogical(source, falselab)
struct symstruct *source;
label_t falselab;
{
    label_t exitlab;
    struct symstruct *target;

    target = constsym((value_t) TRUE);
    target->type = ctype;
    loadreg(target, DREG);
    sbranch(RA, exitlab = getlabel());
    deflabel(falselab);
    target = constsym((value_t) FALSE);
    target->type = ctype;
    *source = *target;
    loadreg(source, DREG);
    outnlabel(exitlab);
}

PRIVATE void logandcond(exp, truelab, falselab, nojump)
struct nodestruct *exp;
label_t truelab;
label_t falselab;
bool_pt nojump;			/* NB if nonzero, is ~0 so complement is 0 */
{
    label_t andlab;

    andlab = getlabel();
    jumpcond(exp->left.nodeptr, andlab, falselab, ~0);
    deflabel(andlab);
    jumpcond(exp->right, truelab, falselab, nojump);
}

PUBLIC void logop(exp)
struct nodestruct *exp;
{
    label_t falselab;
    struct symstruct *target;
    label_t truelab;

    jumpcond(exp, truelab = getlabel(), falselab = getlabel(), ~0);
    deflabel(truelab);
    target = constsym((value_t) 0);	/* anything, loadlogical makes B reg */
    target->type = ctype;
    loadlogical(target, falselab);
    exp->tag = LEAF;
    exp->left.symptr = target;
}

PRIVATE void logorcond(exp, truelab, falselab, nojump)
struct nodestruct *exp;
label_t truelab;
label_t falselab;
bool_pt nojump;			/* NB if nonzero, is ~0 so complement is 0 */
{
    label_t orlab;

    orlab = getlabel();
    jumpcond(exp->left.nodeptr, truelab, orlab, 0);
    deflabel(orlab);
    jumpcond(exp->right, truelab, falselab, nojump);
}

PRIVATE void reduceconst(source)
struct symstruct *source;
{
    if (source->storage == CONSTANT && ischarconst(source->offset.offv) &&
	(source->type->scalar & (CHAR | SHORT | INT | DLONG)) != DLONG)
    {
	if (source->type->scalar & UNSIGNED)
	    source->type = uctype;
	else
	    source->type = ctype;
    }
}

PRIVATE void test(target, pcondtrue)
struct symstruct *target;
ccode_t *pcondtrue;
{
#ifdef I8088
    store_t targreg;
#endif

    *pcondtrue = testcc[*pcondtrue];
    if (target->type->scalar & DLONG)
    {
	long1op(EQOP, target);
	return;
    }
    if (target->type->scalar & RSCALAR)
    {
	float1op(EQOP, target);
	return;
    }
#ifdef I8088
    if (target->indcount != 0 ||
	target->storage == LOCAL && target->offset.offi != sp)
	load(target, DREG);
    if (target->storage == GLOBAL)
	load(target, getindexreg());
    if (target->type->scalar & CHAR)
	load(target, DREG);
    targreg = target->storage;
    if (target->offset.offi != 0 && cc_signed(*pcondtrue))
	load(target, targreg);

    /* Extension was not done in exptree for the == 0 case, to allow
     * optimization here - which we don't do for shorts.  (foo--) is
     * newfoo == -1 here and used to be missed.
     */
    if (i386_32 && target->type->scalar & SHORT)
	extend(target);

    if (target->offset.offi == 0)
    {
	outtest();
	outregname(targreg);
	outcomma();
	outnregname(targreg);
	return;
    }
    outcmp();
    outimadj(-target->offset.offi, targreg);
#endif
#ifdef MC6809
    if (target->indcount != 0 ||
	target->storage == LOCAL && target->offset.offi != sp)
    {
	load(target, DREG);
	return;
    }
    if (cmpsmallconst(0, target, pcondtrue))
	return;
    if (target->storage == GLOBAL)
	load(target, getindexreg());
    if (target->type->scalar & CHAR)
	load(target, DREG);
    if (target->offset.offi != 0 && cc_signed(*pcondtrue))
	load(target, target->storage);
    if (target->type->scalar & CHAR)
    {
	if (target->offset.offi == 0)
	{
	    outtest();
	    outnregname(BREG);
	    return;
	}
	outcmp();
	outimadj(-target->offset.offi, BREG);
    }
    else
    {
	cmporsub(target);
	outimadj(-target->offset.offi, target->storage);
    }
#endif
}

/* test expression and jump depending on NE/EQ */

PRIVATE void testcond(exp, truelab, falselab, nojump)
struct nodestruct *exp;
label_t truelab;
label_t falselab;
bool_pt nojump;			/* NB if nonzero, is ~0 so complement is 0 */
{
    ccode_t condtrue;
    struct symstruct *source;

    makeleaf(exp);
    source = exp->left.symptr;
    reduceconst(source);
    if (source->storage != CONSTANT)
    {
	condtrue = NE;
	test(source, &condtrue);
	if ((bool_t) nojump)
	    lbranch(oppcc[condtrue], falselab);
	else
	    lbranch(condtrue, truelab);
    }
    else if (source->offset.offi == 0)
    {
	if ((bool_t) nojump)
	    jump(falselab);
    }
    else if (!(bool_t) nojump)
	jump(truelab);
}
